2009-02-03 6 views
13

Je me demande ce qu'il faudrait faire quelque chose comme ce travail:C# demande de fonctionnalité: implémenter des interfaces sur les types anonymes

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

Le type anonyme créé ressemblerait à quelque chose comme ceci:

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

Y at-il une raison pour laquelle le compilateur serait incapable de faire quelque chose comme ça? Même pour les méthodes non-nulles ou les méthodes qui prennent des paramètres, le compilateur devrait pouvoir déduire les types de la déclaration d'interface. Bien que je me rende compte que ce n'est pas actuellement possible et qu'il serait plus logique de créer simplement une classe concrète dans ce cas, je suis plus intéressé par les aspects théoriques de cette situation.

+0

Cette idée est venue à l'esprit hier et j'ai essayé de faire une classe '' Anonymous et utiliser 'Reflection.Emit' pour construire une telle classe implémentant l'interface T. Mais il est tooooo cher! Puis j'ai tenté ma chance sur SO et j'ai trouvé cette question, avez-vous des progrès en ce moment? –

Répondre

8

Il y aurait quelques problèmes avec les membres surchargées, indexeurs et implémentations d'interface explicites. Cependant, vous pouvez probablement définir la syntaxe de manière à résoudre ces problèmes. Il est intéressant de noter que vous pouvez vous rapprocher de ce que vous voulez avec C# 3.0 en écrivant une bibliothèque. Fondamentalement, vous pourriez faire ceci:

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

Qui est assez proche de ce que vous voulez. Les principales différences sont un appel à "Create" au lieu du mot-clé "new" et le fait que vous devez spécifier un type de délégué.

La déclaration de « Créer » ressemblerait à ceci:

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

Il utiliserait alors Reflection.Emit pour générer une implémentation d'interface dynamiquement lors de l'exécution. Cependant, cette syntaxe présente des problèmes avec les implémentations d'interface explicites et les membres surchargés, que vous ne pouviez pas résoudre sans modifier le compilateur.

Une alternative serait d'utiliser un initialiseur de collection plutôt qu'un type anonyme. Cela ressemblerait à ceci:

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

qui vous permettra de:

  1. Poignée implémentation d'interface explicite en spécifiant quelque chose comme « IEnumerable.Current » pour le paramètre de chaîne.
  2. Définissez Members.Add pour ne pas avoir à spécifier le type de délégué dans l'initialiseur.

Vous devez faire quelques petites choses à mettre en œuvre ceci:

  1. Writer un petit analyseur pour les noms de type C#. Cela nécessite seulement ".", "[]", "<>", ID, et les noms des types primitifs, donc vous pourriez probablement le faire en quelques heures
  2. Mettre en place un cache de sorte que vous ne générez qu'une seule classe pour chaque interface unique
  3. Implémentez le code Reflection.Emit gen. Cela prendrait probablement environ 2 jours au maximum.
+0

C'était très dans l'esprit de la question - merci! –

-1

Ce ne serait pas possible actuellement.

Quelle serait la différence entre ceci et simplement faire IFoo une classe concrète à la place? On dirait que cela pourrait être la meilleure option.

Qu'est-ce qu'il faudrait? Un nouveau compilateur et des tonnes de contrôles pour s'assurer qu'ils n'ont pas brisé les autres fonctionnalités. Personnellement, je pense qu'il serait plus facile d'exiger que les développeurs créent simplement une version concrète de leur classe.

2

Tant que nous mettons en place une liste de souhaits d'interface, j'aimerais vraiment pouvoir dire au compilateur qu'une classe implémente une interface en dehors de la définition de classe - même dans un assemblage séparé. Par exemple, disons que je travaille sur un programme pour extraire des fichiers de différents formats d'archives. Je veux être capable de tirer dans les implémentations existantes à partir de différentes bibliothèques — dire, SharpZipLib et une implémentation commerciale PGP — et de consommer les deux bibliothèques en utilisant le même code sans créer de nouvelles classes. Ensuite, je pourrais utiliser des types de l'une ou l'autre source dans des contraintes génériques, par exemple.

Une autre utilisation serait de dire au compilateur que System.Xml.Serialization.XmlSerializer implémente l'interface System.Runtime.Serialization.IFormatter (cela est déjà fait, mais le compilateur ne le connaît pas).

Cela pourrait être utilisé pour implémenter votre requête, mais pas automatiquement. Vous devrez toujours en parler explicitement au compilateur. Vous ne savez pas à quoi ressemblera la syntaxe, car vous devrez toujours mapper manuellement les méthodes et les propriétés quelque part, ce qui signifie beaucoup de verbiage. Peut-être quelque chose de similaire aux méthodes d'extension.

+0

hein? "Il le fait déjà, mais le compilateur ne le sait pas" 1. Non, ce n'est pas le cas. 2. Il implémente Serialize/Deserialize, mais pas les trois propriétés Binder, Context, SurrogateSelector. –

+0

Eh: vous pourriez le chausse dans assez facile. Il aurait dû être construit afin que vous puissiez avoir une méthode qui accepte l'instance IFormatter et passer BinaryFormatter, SoapFormatter, le XmlSerializer existant, ou votre propre implémentation IFormatter sans se plaindre. –

+0

Savez-vous que Oxygene [a ceci] (http://prismwiki.codegear.com/fr/Provide_Mixin-like_functionality)? J'ai également décrit une fonctionnalité que j'aimerais voir [ici] (http://codecrafter.blogspot.com/2010/10/roles-in-c.html). –

4

Un type anonyme ne peut pas être fait pour faire quoi que ce soit, sauf pour avoir des propriétés en lecture seule.

Citant les C# Programming Guide (Anonymous Types):

« Les types anonymes sont les types de classes qui se composent d'un ou plusieurs publics propriétés en lecture seule sont autorisés Pas d'autres types de membres de classe tels que les méthodes ou événements.. Un type anonyme ne peut pas être cast à n'importe quelle interface ou type à l'exception de l'objet. "

+3

Oui, mais j'espère pouvoir réviser cette définition :) –

0

Idée intéressante, je serais un peu préoccupé par le fait que même si cela pouvait être fait, il pourrait être déroutant. Par exemple.lors de la définition d'une propriété avec des setters et getters non triviaux, ou comment désamorcer Foo si le type déclarant contenait également une propriété appelée Foo.

Je me demande si ce serait plus facile dans un langage plus dynamique, ou même avec le type dynamique et DLR en C# 4.0?

Peut-être aujourd'hui en C# une partie de l'intention pourrait être atteint avec lambdas:

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

Vous pourriez avoir quelque chose comme anonymous classes en 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(); 
} 
-1

Je l'ai utilisé en Java la classe Amonimous par la « nouvelle IFoo() {...} » SINTAX et il est pratique et facile quand vous devez implémenter rapidement une interface simple.

Comme un échantillon, il serait bon de mettre en œuvre IDisposable ainsi sur un objet héritage utilisé juste une fois au lieu de dériver une nouvelle classe pour la mettre en œuvre.

6

il faut C# 4, mais le cadre opensource impromptu interface pouvez simuler ceci hors de la boîte en utilisant proxies DLR en interne. la performance est bonne, mais pas aussi bon que si le changement que vous avez proposé l'existence.

using ImpromptuInterface.Dynamic; 

...

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

serait-ce pas cool. Inline class anonymous:

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

Je vais vider ça ici. Je l'ai écrit il y a un moment mais IIRC ça marche bien.

D'abord une fonction d'assistance pour prendre un MethodInfo et retourner un Type d'un correspondant Func ou Action. Vous avez besoin d'une branche pour chaque nombre de paramètres, malheureusement, et j'ai apparemment arrêté à trois heures.

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

Et maintenant la méthode qui prend une interface en tant que paramètre générique et renvoie une Type qui implémente l'interface et a un constructeur (doit être appelé par Activator.CreateInstance) prendre un Func ou Action pour chaque méthode/lecture/setter. Cependant, vous devez connaître le bon ordre pour les mettre dans le constructeur. Alternativement (code commenté) il peut générer une DLL que vous pouvez ensuite référencer et utiliser directement le type.

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

Vous pouvez utiliser ceci comme p. Ex.

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