Question

Je me demande ce qu'il faudrait faire quelque chose comme ce travail:

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 raison que le compilateur serait incapable de faire quelque chose comme ça? Même pour des méthodes non vides ou des méthodes qui prennent des paramètres du compilateur devrait être en mesure de déduire les types de la déclaration d'interface.

Disclaimer:. Alors que je me rends compte que ce n'est pas possible actuellement et 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 ce

Était-ce utile?

La solution

Il y aurait quelques problèmes avec les membres surchargées, indexeurs et implémentations d'interface explicites.

Cependant, vous pouvez définir probablement la syntaxe d'une manière qui vous permet de résoudre ces problèmes.

Fait intéressant, vous pouvez obtenir assez proche de ce que vous voulez avec C # 3.0 en écrivant une bibliothèque. En gros, vous pouvez faire ceci:

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

Ce qui est assez proche de ce que vous voulez. Les principales différences sont un appel à « Créer » au lieu du mot-clé « nouveau » 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 serait alors utiliser Reflection.Emit pour générer une implémentation d'interface dynamiquement lors de l'exécution.

Cette syntaxe, cependant, n'ont des problèmes avec les implémentations d'interface explicites et membres surchargées, que vous ne pouviez pas résoudre sans changer le compilateur.

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

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

Cela 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éfinir Members.Add afin que vous n'avez pas besoin de spécifier le type de délégué dans le initialiseur.

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

  1. Writer un petit analyseur pour les noms de type C #. Cela ne nécessite « », « [] », « <> », ID, et les noms de type primitif, de sorte que vous pourriez probablement faire en quelques heures
  2. Mettre en œuvre un cache de sorte que vous générez une seule classe pour chaque interface unique
  3. Mettre en œuvre le code de Reflection.Emit gen. Ce serait probablement prendre environ 2 jours au maximum.

Autres conseils

Il faut c # 4, mais le cadre opensource Interface impromptue pouvez simuler ceci hors de la boîte en utilisant DLR procurations 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))
            });

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

C # Guide de programmation (types anonymes) :

  

"Les types anonymes sont les types de classes qui   consister en un ou plusieurs publics   propriétés en lecture seule. Aucun autre type   des membres de la catégorie telles que les méthodes ou   les événements sont autorisés. Un type anonyme   ne peut pas être jeté à une interface ou   type, sauf pour l'objet. "

Tant que nous mettons sur une liste de souhaits d'interface, je voudrais vraiment être en mesure de dire au compilateur qu'une classe implémente une interface en dehors de la classe definition- même dans un ensemble séparé.

Par exemple, disons que je travaille sur un programme pour extraire les fichiers de différents formats d'archives. Je veux être en mesure de tirer dans les implémentations existantes de différentes bibliothèques - dire, SharpZipLib et une mise en œuvre de PGP commerciale - et consume les deux bibliothèques en utilisant le même code sans créer de nouvelles classes. Je pourrais utiliser des types de sources, soit des contraintes génériques, par exemple.

Une autre utilisation serait dit au compilateur que l'implémente System.Xml.Serialization.XmlSerializer l'interface System.Runtime.Serialization.IFormatter (elle le fait déjà, mais le compilateur ne le sait pas).

Cela pourrait être utilisé pour mettre en œuvre votre demande ainsi, mais pas automatiquement. Vous auriez encore dire explicitement le compilateur à ce sujet. Je ne sais pas comment la syntaxe ressemblerait, parce que vous auriez encore la carte manuellement méthodes et propriétés quelque part, ce qui signifie beaucoup de verbiage. Peut-être quelque chose de similaire à des méthodes d'extension.

Vous pouvez avoir quelque chose comme classes anonymes 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(); 
} 

Ne serait-ce pas cool. Classe anonyme en ligne:

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

Je vais vider ce ici. Je l'ai écrit il y a quelque temps, mais IIRC il fonctionne bien.

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

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 qui implémente l'Activator.CreateInstance interface et a un constructeur (doit être appelée via <=>) ou prendre un <=> pour chaque <=> procédé / lecture / définition. Vous devez connaître le bon ordre de les mettre dans le constructeur, cependant. Vous pouvez également (code commenté-out), 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 l'utiliser comme par exemple.

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

Idée intéressante, je serais un peu inquiet que même si cela pourrait se faire, il pourrait se prêter à confusion. Par exemple. lors de la définition d'une propriété avec les organismes non-triviales et getters, ou comment Foo désambiguïser si le type déclarant contenait également une propriété appelée Foo.

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

Peut-être aujourd'hui en C # certains des pourrait être atteint avec l'intention 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;}
}

Ce ne serait pas possible actuellement.

Quelle serait la différence entre cela 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 veiller à ce qu'ils ne cassent pas les autres caractéristiques. Personnellement, je pense que ce serait être plus facile d'exiger des développeurs de créer simplement une version concrète de leur classe.

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 une héritage objet utilisé juste une fois au lieu de dériver une nouvelle classe pour la mettre en œuvre.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top