Domanda

Mi chiedo cosa ci vorrebbe per far funzionare qualcosa di simile:

using System;

class Program
{
    static void Main()
    {
        var f = new IFoo { 
                    Foo = "foo",
                    Print = () => Console.WriteLine(Foo)
            };
    }
}

interface IFoo
{
    String Foo { get; set; }
    void Print();
}

Il tipo anonimo creato sarebbe simile a questo:

internal sealed class <>f__AnonymousType0<<Foo>j__TPar> : IFoo
{
    readonly <Foo>j__TPar <Foo>i__Field;

    public <>f__AnonymousType0(<Foo>j__TPar Foo)
    {
        this.<Foo>i__Field = Foo;
    }

    public <Foo>j__TPar Foo
    {
        get { return this.<Foo>i__Field; }
    }

    public void Print()
    {
        Console.WriteLine(this.Foo);
    }
}

C'è qualche motivo per cui il compilatore non sarebbe in grado di fare qualcosa del genere?Anche per metodi non void o metodi che accettano parametri il compilatore dovrebbe essere in grado di dedurre i tipi dalla dichiarazione dell'interfaccia.

Disclaimer: Anche se mi rendo conto che ciò non è attualmente possibile e che in questo caso avrebbe più senso creare semplicemente una classe concreta, sono più interessato agli aspetti teorici di questo.

È stato utile?

Soluzione

Ci sarebbero alcuni problemi con i membri del sovraccarico, indicizzatori, e le implementazioni di interfaccia esplicite.

Tuttavia, si potrebbe forse definire la sintassi in un modo che permette di risolvere questi problemi.

È interessante notare che è possibile ottenere abbastanza vicino a quello che si vuole con C # 3.0 scrivendo una biblioteca. In sostanza, si potrebbe fare questo:

Create<IFoo>
(
    new
    {
        Foo = "foo",
        Print = (Action)(() => Console.WriteLine(Foo))
    }
);

Il che è abbastanza vicino a quello che si desidera. Le differenze principali sono un invito a "Crea" anziché la parola chiave "nuovo" e il fatto che è necessario specificare un tipo delegato.

La dichiarazione di "Create" sarebbe simile a questa:

T Create<T> (object o)
{
//...
}

Sarebbe quindi utilizzare Reflection.Emit per generare un'implementazione dell'interfaccia dinamicamente in fase di esecuzione.

Questa sintassi, tuttavia, non ha problemi con le implementazioni di interfaccia esplicite e membri sovraccarico, che non si poteva risolvere senza cambiare il compilatore.

Un'alternativa sarebbe quella di utilizzare un inizializzatore di insieme, piuttosto che un tipo anonimo. Che sarebbe simile a questa:

Create
{
    new Members<IFoo>
    {
        {"Print", ((IFoo @this)=>Console.WriteLine(Foo))},
        {"Foo", "foo"}
    }
}

che permetta di:

  1. Maniglia implementazione dell'interfaccia esplicita specificando qualcosa come "IEnumerable.Current" per il parametro di stringa.
  2. Definisci Members.Add in modo che non c'è bisogno di specificare il tipo di delegato nel inizializzatore.

Si avrebbe bisogno di fare un paio di cose da implementare questa:

  1. Writer un piccolo parser per i nomi di tipo C #. Questo richiede solo "", '[]', '<>', ID, e il tipo primitivo nomi, così si potrebbe probabilmente fare che in poche ore
  2. Implementare una cache in modo che si genera una sola classe per ogni interfaccia unica
  3. Implementare il codice Reflection.Emit gen. Questo sarebbe probabilmente prendere circa 2 giorni al massimo.

Altri suggerimenti

Si richiede c # 4, ma il interfaccia improvvisato può fingere questo fuori dalla scatola usando DLR proxy internamente. La prestazione è buona, anche se non è buono come se il cambiamento che ha proposto l'esistenza.

using ImpromptuInterface.Dynamic;

...

var f = ImpromptuGet.Create<IFoo>(new{ 
                Foo = "foo",
                Print = ReturnVoid.Arguments(() => Console.WriteLine(Foo))
            });

Un tipo anonimo non può essere fatto per fare altro che di avere proprietà di sola lettura.

C Guida # di programmazione (tipi anonimi) :

  

"tipi anonimi sono tipi di classe che   costituiti da uno o più pubblici   proprietà di sola lettura. Non ci sono altri tipi   di classe membri quali metodi o   gli eventi sono ammessi. Un tipo anonimo   non può essere gettato a qualsiasi interfaccia o   tipo tranne che per oggetto ".

Finché stiamo mettendo fuori un elenco di interfaccia desiderio, mi piacerebbe davvero essere in grado di dire al compilatore che una classe implementa un'interfaccia di fuori della classe definizione-anche in un assembly separato.

Per esempio, diciamo che sto lavorando a un programma per estrarre i file di diversi formati di archivio. Voglio essere in grado di tirare in implementazioni esistenti da diverse librerie - dico, SharpZipLib e un'implementazione PGP commerciale - e consumano entrambe le librerie utilizzando lo stesso codice senza creare nuove classi. Poi ho potuto usare tipi da entrambe le fonti di vincoli generici, per esempio.

Un altro utilizzo sarebbe dicendo al compilatore che System.Xml.Serialization.XmlSerializer implementa l'interfaccia di System.Runtime.Serialization.IFormatter (lo fa già, ma il compilatore non lo sa).

Questo potrebbe essere utilizzato per implementare la vostra richiesta, così, semplicemente non automaticamente. Avresti ancora dire esplicitamente al compilatore su di esso. Non sono sicuro di come la sintassi sarà, perché dovresti ancora mappare manualmente metodi e proprietà da qualche parte, il che significa un sacco di verbosità. Forse qualcosa di simile a metodi di estensione.

Si potrebbe avere qualcosa di simile classi anonime in Java:

using System; 

class Program { 
  static void Main() { 
    var f = new IFoo() {  
      public String Foo { get { return "foo"; } } 
      public void Print() { Console.WriteLine(Foo); }
    }; 
  } 
} 

interface IFoo { 
  String Foo { get; set; } 
  void Print(); 
} 

Non sarebbe cool. Inline classe anonima:

List<Student>.Distinct(new IEqualityComparer<Student>() 
{ 
    public override bool Equals(Student x, Student y)
    {
        return x.Id == y.Id;
    }

    public override int GetHashCode(Student obj)
    {
        return obj.Id.GetHashCode();
    }
})

ho intenzione di scaricare questo qui. L'ho scritto qualche tempo fa, ma IIRC funziona bene.

In primo luogo una funzione di supporto per prendere un MethodInfo e restituire un Type di un abbinamento Func o Action. Avete bisogno di un ramo per ogni numero di parametri, purtroppo, e mi pare fermato a tre.

static Type GenerateFuncOrAction(MethodInfo method)
{
    var typeParams = method.GetParameters().Select(p => p.ParameterType).ToArray();
    if (method.ReturnType == typeof(void))
    {
        if (typeParams.Length == 0)
        {
            return typeof(Action);
        }
        else if (typeParams.Length == 1)
        {
            return typeof(Action<>).MakeGenericType(typeParams);
        }
        else if (typeParams.Length == 2)
        {
            return typeof(Action<,>).MakeGenericType(typeParams);
        }
        else if (typeParams.Length == 3)
        {
            return typeof(Action<,,>).MakeGenericType(typeParams);
        }
        throw new ArgumentException("Only written up to 3 type parameters");
    }
    else
    {
        if (typeParams.Length == 0)
        {
            return typeof(Func<>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray());
        }
        else if (typeParams.Length == 1)
        {
            return typeof(Func<,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray());
        }
        else if (typeParams.Length == 2)
        {
            return typeof(Func<,,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray());
        }
        else if (typeParams.Length == 3)
        {
            return typeof(Func<,,,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray());
        }
        throw new ArgumentException("Only written up to 3 type parameters");
    }
}

E ora il metodo che accetta un'interfaccia come parametro generico e restituisce un Activator.CreateInstance che implementa l'interfaccia e ha un costruttore (deve essere chiamato tramite <=>) prendendo un <=> o <=> per ogni metodo / getter / setter. È necessario conoscere l'ordine giusto per metterli nel costruttore, però. In alternativa (codice commentato-out) che può generare una DLL, che si può quindi fare riferimento e utilizzare il tipo direttamente.

static Type GenerateInterfaceImplementation<TInterface>()
{
    var interfaceType = typeof(TInterface);
    var funcTypes = interfaceType.GetMethods().Select(GenerateFuncOrAction).ToArray();

    AssemblyName aName =
        new AssemblyName("Dynamic" + interfaceType.Name + "WrapperAssembly");
    var assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
            aName,
            AssemblyBuilderAccess.Run/*AndSave*/); // to get a DLL

    var modBuilder = assBuilder.DefineDynamicModule(aName.Name/*, aName.Name + ".dll"*/); // to get a DLL

    TypeBuilder typeBuilder = modBuilder.DefineType(
        "Dynamic" + interfaceType.Name + "Wrapper",
            TypeAttributes.Public);

    // Define a constructor taking the same parameters as this method.
    var ctrBuilder = typeBuilder.DefineConstructor(
        MethodAttributes.Public | MethodAttributes.HideBySig |
            MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
        CallingConventions.Standard,
        funcTypes);


    // Start building the constructor.
    var ctrGenerator = ctrBuilder.GetILGenerator();
    ctrGenerator.Emit(OpCodes.Ldarg_0);
    ctrGenerator.Emit(
        OpCodes.Call,
        typeof(object).GetConstructor(Type.EmptyTypes));

    // For each interface method, we add a field to hold the supplied
    // delegate, code to store it in the constructor, and an
    // implementation that calls the delegate.
    byte methodIndex = 0;
    foreach (var interfaceMethod in interfaceType.GetMethods())
    {
        ctrBuilder.DefineParameter(
            methodIndex + 1,
            ParameterAttributes.None,
            "del_" + interfaceMethod.Name);

        var delegateField = typeBuilder.DefineField(
            "del_" + interfaceMethod.Name,
            funcTypes[methodIndex],
            FieldAttributes.Private);

        ctrGenerator.Emit(OpCodes.Ldarg_0);
        ctrGenerator.Emit(OpCodes.Ldarg_S, methodIndex + 1);
        ctrGenerator.Emit(OpCodes.Stfld, delegateField);

        var metBuilder = typeBuilder.DefineMethod(
            interfaceMethod.Name,
            MethodAttributes.Public | MethodAttributes.Virtual |
                MethodAttributes.Final | MethodAttributes.HideBySig |
                MethodAttributes.NewSlot,
            interfaceMethod.ReturnType,
            interfaceMethod.GetParameters()
                .Select(p => p.ParameterType).ToArray());

        var metGenerator = metBuilder.GetILGenerator();
        metGenerator.Emit(OpCodes.Ldarg_0);
        metGenerator.Emit(OpCodes.Ldfld, delegateField);

        // Generate code to load each parameter.
        byte paramIndex = 1;
        foreach (var param in interfaceMethod.GetParameters())
        {
            metGenerator.Emit(OpCodes.Ldarg_S, paramIndex);
            paramIndex++;
        }
        metGenerator.EmitCall(
            OpCodes.Callvirt,
            funcTypes[methodIndex].GetMethod("Invoke"),
            null);

        metGenerator.Emit(OpCodes.Ret);
        methodIndex++;
    }

    ctrGenerator.Emit(OpCodes.Ret);

    // Add interface implementation and finish creating.
    typeBuilder.AddInterfaceImplementation(interfaceType);
    var wrapperType = typeBuilder.CreateType();
    //assBuilder.Save(aName.Name + ".dll"); // to get a DLL

    return wrapperType;
}

È possibile utilizzare questo come per es.

public interface ITest
{
    void M1();
    string M2(int m2, string n2);
    string prop { get; set; }

    event test BoopBooped;
}

Type it = GenerateInterfaceImplementation<ITest>();
ITest instance = (ITest)Activator.CreateInstance(it,
    new Action(() => {Console.WriteLine("M1 called"); return;}),
    new Func<int, string, string>((i, s) => "M2 gives " + s + i.ToString()),
    new Func<String>(() => "prop value"),
    new Action<string>(s => {Console.WriteLine("prop set to " + s);}),
    new Action<test>(eh => {Console.WriteLine(eh("handler added"));}),
    new Action<test>(eh => {Console.WriteLine(eh("handler removed"));}));

// or with the generated DLL
ITest instance = new DynamicITestWrapper(
    // parameters as before but you can see the signature
    );

Idea interessante, sarei un po 'preoccupato che, anche se si poteva fare che potrebbe creare confusione. Per esempio. quando si definisce una proprietà con incastonatori non banali e getter, o come disambiguare Foo se il tipo che dichiara conteneva anche una proprietà chiamata Foo.

Mi chiedo se questo sarebbe più facile in un linguaggio più dinamico, o anche con il tipo dinamico e DLR in C # 4.0?

Forse oggi in C # alcuni dei intento potrebbe essere raggiunto con lambda:

void Main() {
    var foo = new Foo();
    foo.Bar = "bar";
    foo.Print = () => Console.WriteLine(foo.Bar);
    foo.Print();
}


class Foo : IFoo {
    public String Bar { get; set; }    
    public Action Print {get;set;}
}

Questo non sarebbe stato possibile al momento.

Quale sarebbe la differenza tra questo e semplicemente facendo IFoo una classe concreta, invece? Sembra che potrebbe essere l'opzione migliore.

Che cosa ci vorrebbe? Un nuovo compilatore e tonnellate di controlli per garantire non gli spezzarono le altre caratteristiche. Personalmente, penso che sarebbe solo essere più facile da richiedere agli sviluppatori di creare solo una versione concreta della loro classe.

Ho utilizzato in Java la Classe Amonimous tramite la sintassi "new IFoo(){...}" ed è pratica e facile quando si deve implementare velocemente una semplice interfaccia.

Come esempio, sarebbe carino implementare IDisposable in questo modo su a eredità oggetto appena usato Una volta invece di derivare una nuova classe per implementarla.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top