2009-02-03 2 views
13

Ich frage mich, was es dauern würde, so etwas wie diese Arbeit machen:C# Feature Anfrage: implementieren Schnittstellen auf anonyme Typen

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(); 
} 

Der anonyme Typ etwas geschaffen würde wie folgt aussehen:

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); 
    } 
} 

Gibt es einen Grund, dass der Compiler so etwas nicht tun könnte? Auch bei nicht void-Methoden oder Methoden, die Parameter übernehmen, sollte der Compiler die Typen aus der Interface-Deklaration ableiten können.

Haftungsausschluss: Während mir klar ist, dass dies derzeit nicht möglich ist und es sinnvoller wäre, in diesem Fall einfach eine konkrete Klasse zu erstellen, interessiere ich mich mehr für die theoretischen Aspekte.

+0

Diese Idee kam mir in den Sinn gestern und ich versuchte, eine Klasse 'Anonymous ' herzustellen und zu verwenden 'Reflection.Emit' zu bauen eine solche Klasse der Umsetzung der Schnittstelle T. Aber es ist tooooo teuer! Dann habe ich mein Glück bei SO versucht und diese Frage gefunden, hast du jetzt Fortschritte? –

Antwort

8

ein paar Probleme mit überladenen Mitgliedern, Indexer und expliziten Schnittstellenimplementierungen Es sei.

Sie könnten jedoch wahrscheinlich die Syntax so definieren, dass Sie diese Probleme lösen können.

Interessanterweise können Sie mit C# 3.0 ziemlich nah an das kommen, was Sie wollen, indem Sie eine Bibliothek schreiben. Grundsätzlich könnten Sie das tun:

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

Welches ist ziemlich nah an dem, was Sie wollen. Die Hauptunterschiede sind ein Aufruf von "Create" anstelle des Schlüsselworts "new" und die Tatsache, dass Sie einen Delegatentyp angeben müssen.

Die Erklärung des „Create“ würde wie folgt aussehen:

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

Es wäre dann Reflection.Emit verwenden, um eine Interface-Implementierung zur Laufzeit dynamisch zu generieren.

Diese Syntax hat jedoch Probleme mit expliziten Schnittstellenimplementierungen und überladenen Membern, die Sie ohne Änderung des Compilers nicht auflösen konnten.

Eine Alternative wäre die Verwendung eines Auflistungsinitialisierers anstelle eines anonymen Typs. Das würde wie folgt aussehen:

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

, die Sie würde es ermöglichen: explizite Schnittstellenimplementierung

  1. Griff durch die Angabe so etwas wie „IEnumerable.Current“ für die String-Parameter.
  2. Definieren Sie Members.Add, sodass Sie den Delegatentyp im Initialisierer nicht angeben müssen.

Sie müssen ein paar Dinge tun, um dies zu implementieren:

  1. Writer einen kleiner Parser für C# Typnamen. Dies erfordert nur ".", "[]", "<>", ID und die Namen primitiver Typen, so dass Sie das wahrscheinlich in ein paar Stunden tun könnten
  2. Implementieren Sie einen Cache, so dass Sie nur eine einzige Klasse für generieren Jede eindeutige Schnittstelle
  3. Implementieren Sie den Reflection.Emit Code Gen. Dies würde wahrscheinlich ungefähr 2 Tage dauern.
+0

Dies war sehr im Geiste der Frage - danke! –

-1

Dies wäre zur Zeit nicht möglich.

Was wäre der Unterschied zwischen dieser und IFoo einfach eine konkrete Klasse zu machen? Scheint so, als ob das die bessere Option wäre.

Was würde es dauern? Ein neuer Compiler und jede Menge Überprüfungen, um sicherzustellen, dass sie die anderen Funktionen nicht durchbrechen. Ich persönlich denke, es wäre einfacher, Entwickler dazu zu zwingen, einfach eine konkrete Version ihrer Klasse zu erstellen.

2

Solange wir eine Interface-Wunschliste veröffentlichen, würde ich gerne dem Compiler mitteilen können, dass eine Klasse eine Schnittstelle außerhalb der Klassendefinition implementiert - sogar in einer separaten Assembly.

Angenommen, ich arbeite an einem Programm zum Extrahieren von Dateien aus verschiedenen Archivformaten. Ich möchte in der Lage sein, bestehende Implementierungen aus verschiedenen Bibliotheken — sagen, SharpZipLib und eine kommerzielle PGP-Implementierung — und verbrauchen beide Bibliotheken mit dem gleichen Code ohne neue Klassen erstellen. Dann könnte ich zum Beispiel Typen aus beiden Quellen in generischen Constraints verwenden.

Eine andere Verwendung würde dem Compiler sagen, dass System.Xml.Serialization.XmlSerializer die Schnittstelle System.Runtime.Serialization.IFormatter implementiert (es tut es bereits, aber der Compiler weiß es nicht).

Dies könnte auch verwendet werden, um Ihre Anfrage zu implementieren, nur nicht automatisch. Sie müssen dem Compiler das explizit mitteilen. Ich bin mir nicht sicher, wie die Syntax aussehen würde, weil Sie Methoden und Eigenschaften immer noch manuell zuordnen müssen, was eine Menge Wortspiel bedeutet. Vielleicht etwas ähnlich zu Erweiterungsmethoden.

+0

nicht wahr? "Es tut schon, aber der Compiler weiß es nicht" 1. Nein, tut es nicht. 2. Es implementiert Serialize/Deserialize, aber nicht die drei Eigenschaften Binder, Context, SurrogateSelector. –

+0

Eh: Sie könnten es in ziemlich einfach schuhen. Es _should_ wurde erstellt, damit Sie eine Methode haben können, die IFormatter-Instanz akzeptiert und BinaryFormatter, SoapFormatter, den vorhandenen XmlSerializer oder Ihre eigene IFormatter-Implementierung ohne Beanstandung übergibt. –

+0

Wissen Sie, dass Oxygene [hat dies] (http://prismwiki.codegear.com/en/Provide_Mixin-like_functionality)? Ich habe auch ein Feature beschrieben, das ich hier sehen möchte (http://codecrafter.blogspot.com/2010/10/roles-in-c.html). –

4

Ein anonymer Typ kann nur mit Leseeigenschaften versehen werden.

die C# Programming Guide (Anonymous Types) Zitiert:

„Anonyme Typen sind Klassentypen, die von einem oder mehreren öffentlichen Nur-Lese-Eigenschaften Keine andere Arten von den Teilnehmern wie Methoden oder Ereignisse dürfen bestehen .. Ein anonymer Typ kann nicht in eine Schnittstelle umgewandelt werden oder Typ außer Objekt. "

+3

Ja, aber ich hoffe, diese Definition zu revidieren :) –

0

Interessante Idee, ich wäre ein wenig besorgt, dass selbst wenn es getan werden könnte, könnte es verwirrend werden. Z.B.wenn Sie eine Eigenschaft mit nicht-trivialen Settern und Gettern definieren oder Foo disambiguieren, wenn der deklarierende Typ auch eine Eigenschaft namens Foo enthält.

Ich frage mich, ob dies in einer dynamischeren Sprache oder sogar mit dem dynamischen Typ und DLR in C# 4.0 einfacher wäre?

Vielleicht heute in C# einige der Absicht mit lambdas erreicht werden können:

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;} 
} 
1

Man könnte so etwas wie anonymous classes in Java haben:

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(); 
} 
-1

ich in Java die Amonimous Klasse durch die „neue IFoo verwendet haben() {...}“Sintax und es ist praktisch und einfach, wenn Sie müssen schnell eine einfache Schnittstelle implementieren.

Als Beispiel wäre es schön, IDisposable auf diese Weise auf einem Vermächtnis Objekt nur einmal verwendet zu implementieren stattdessen ein Ableiten neue Klasse, sie umzusetzen.

6

es erfordert C# 4, aber die Open-Source-Framework impromptu interface fälschen kann dies aus der Box Proxies intern DLR verwendet wird. die Leistung ist gut aber nicht so gut, wie wenn die Änderung Sie existiert vorschlagen.

using ImpromptuInterface.Dynamic; 

...

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

Wäre das nicht cool. Inline anonyme Klasse:

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(); 
    } 
}) 
1

Ich werde dies hier ausgeben. Ich habe es vor einer Weile geschrieben, aber IIRC, es funktioniert OK.

Zuerst eine Hilfsfunktion, um einen MethodInfo zu nehmen und einen Type eines zusammenpassenden Func oder Action zurückzugeben. Sie brauchen leider eine Verzweigung für jede Anzahl von Parametern, und ich habe anscheinend um drei angehalten.

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"); 
    } 
} 

Und nun das Verfahren, das eine Schnittstelle als ein generischer Parameter nimmt und gibt ein Type, die die Schnittstelle implementiert und hat einen Konstruktor (muss über Activator.CreateInstance genannt werden), um ein Func oder Action für jede Methode/Getter Einnahme/Setter. Sie müssen jedoch die richtige Reihenfolge kennen, um sie in den Konstruktor zu stellen. Alternativ (auskommentierter Code) kann er eine DLL generieren, auf die Sie dann verweisen und den Typ direkt verwenden können.

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; 
} 

Sie können dies z.B.

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 
    );