c# java - Comment utiliser la réflexion pour appeler une méthode générique?





generic (7)


Personne n'a fourni la solution " Reflection classique ", donc voici un exemple de code complet:

using System;
using System.Collections;
using System.Collections.Generic;

namespace DictionaryRuntime
{
    public class DynamicDictionaryFactory
    {
        /// <summary>
        /// Factory to create dynamically a generic Dictionary.
        /// </summary>
        public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType)
        {
            //Creating the Dictionary.
            Type typeDict = typeof(Dictionary<,>);

            //Creating KeyValue Type for Dictionary.
            Type[] typeArgs = { keyType, valueType };

            //Passing the Type and create Dictionary Type.
            Type genericType = typeDict.MakeGenericType(typeArgs);

            //Creating Instance for Dictionary<K,T>.
            IDictionary d = Activator.CreateInstance(genericType) as IDictionary;

            return d;

        }
    }
}

La classe DynamicDictionaryFactory ci-dessus a une méthode

CreateDynamicGenericInstance(Type keyType, Type valueType)

et il crée et renvoie une instance IDictionary, dont les types de clés et de valeurs sont exactement ceux spécifiés dans l'appel keyType et valueType .

Voici un exemple complet comment appeler cette méthode pour instancier et utiliser un Dictionary<String, int> :

using System;
using System.Collections.Generic;

namespace DynamicDictionary
{
    class Test
    {
        static void Main(string[] args)
        {
            var factory = new DictionaryRuntime.DynamicDictionaryFactory();
            var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int));

            var typedDict = dict as Dictionary<String, int>;

            if (typedDict != null)
            {
                Console.WriteLine("Dictionary<String, int>");

                typedDict.Add("One", 1);
                typedDict.Add("Two", 2);
                typedDict.Add("Three", 3);

                foreach(var kvp in typedDict)
                {
                    Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value);
                }
            }
            else
                Console.WriteLine("null");
        }
    }
}

Lorsque l'application console ci-dessus est exécutée, nous obtenons le résultat correct et attendu:

Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3

Quelle est la meilleure façon d'appeler une méthode générique lorsque le paramètre type n'est pas connu au moment de la compilation, mais est obtenu dynamiquement au moment de l'exécution?

Considérons l'exemple de code suivant - dans la méthode Example() , quelle est la manière la plus concise d'invoquer GenericMethod<T>() utilisant le Type stocké dans la variable myType ?

public class Sample
{
    public void Example(string typeName)
    {
        Type myType = FindType(typeName);

        // What goes here to call GenericMethod<T>()?
        GenericMethod<myType>(); // This doesn't work

        // What changes to call StaticMethod<T>()?
        Sample.StaticMethod<myType>(); // This also doesn't work
    }

    public void GenericMethod<T>()
    {
        // ...
    }

    public static void StaticMethod<T>()
    {
        //...
    }
}



Vous devez utiliser la réflexion pour lancer la méthode, puis la "construire" en fournissant des arguments de type avec MakeGenericMethod :

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

Pour une méthode statique, passez null comme premier argument à Invoke . Cela n'a rien à voir avec les méthodes génériques - c'est juste une réflexion normale.

Comme indiqué, beaucoup de ceci est plus simple à partir de C # 4 utilisant la dynamic - si vous pouvez utiliser l'inférence de type, bien sûr. Cela n'aide pas dans les cas où l'inférence de type n'est pas disponible, comme l'exemple exact dans la question.




L'appel d'une méthode générique avec un paramètre de type connu seulement à l'exécution peut être grandement simplifié en utilisant un type dynamic au lieu de l'API de réflexion.

Pour utiliser cette technique, le type doit être connu de l'objet réel (pas seulement une instance de la classe Type ). Sinon, vous devez créer un objet de ce type ou utiliser la solution API de réflexion standard. Vous pouvez créer un objet en utilisant la méthode Activator.CreateInstance .

Si vous voulez appeler une méthode générique, celle de l'utilisation "normale" aurait eu son type inféré, alors il s'agit simplement de lancer l'objet de type inconnu à dynamic . Voici un exemple:

class Alpha { }
class Beta { }
class Service
{
    public void Process<T>(T item)
    {
        Console.WriteLine("item.GetType(): " + item.GetType()
                          + "\ttypeof(T): " + typeof(T));
    }
}

class Program
{
    static void Main(string[] args)
    {
        var a = new Alpha();
        var b = new Beta();

        var service = new Service();
        service.Process(a); // Same as "service.Process<Alpha>(a)"
        service.Process(b); // Same as "service.Process<Beta>(b)"

        var objects = new object[] { a, b };
        foreach (var o in objects)
        {
            service.Process(o); // Same as "service.Process<object>(o)"
        }
        foreach (var o in objects)
        {
            dynamic dynObj = o;
            service.Process(dynObj); // Or write "service.Process((dynamic)o)"
        }
    }
}

Et voici la sortie de ce programme:

item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta
item.GetType(): Alpha    typeof(T): System.Object
item.GetType(): Beta     typeof(T): System.Object
item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta

Process est une méthode d'instance générique qui écrit le type réel de l'argument passé (en utilisant la méthode GetType() ) et le type du paramètre générique (en utilisant l'opérateur typeof ).

En convertissant l'argument object en type dynamic nous avons reporté la fourniture du paramètre type jusqu'à l'exécution. Lorsque la méthode Process est appelée avec l'argument dynamic , le compilateur ne se soucie pas du type de cet argument. Le compilateur génère du code qui, lors de l'exécution, vérifie les types réels d'arguments passés (en utilisant la réflexion) et choisit la meilleure méthode à appeler. Ici, il n'y a qu'une seule méthode générique, donc elle est invoquée avec un paramètre de type correct.

Dans cet exemple, la sortie est la même que si vous écriviez:

foreach (var o in objects)
{
    MethodInfo method = typeof(Service).GetMethod("Process");
    MethodInfo generic = method.MakeGenericMethod(o.GetType());
    generic.Invoke(service, new object[] { o });
}

La version avec un type dynamique est nettement plus courte et plus facile à écrire. Vous ne devriez pas non plus vous soucier des performances de l'appel de cette fonction plusieurs fois. L'appel suivant avec des arguments du même type devrait être plus rapide grâce au mécanisme de caching en caching dans DLR. Bien sûr, vous pouvez écrire du code qui invoque des délégués, mais en utilisant le type dynamic vous obtenez ce comportement gratuitement.

Si la méthode générique que vous voulez appeler n'a pas d'argument de type paramétré (donc son paramètre de type ne peut pas être déduit), alors vous pouvez envelopper l'invocation de la méthode générique dans une méthode auxiliaire comme dans l'exemple suivant:

class Program
{
    static void Main(string[] args)
    {
        object obj = new Alpha();

        Helper((dynamic)obj);
    }

    public static void Helper<T>(T obj)
    {
        GenericMethod<T>();
    }

    public static void GenericMethod<T>()
    {
        Console.WriteLine("GenericMethod<" + typeof(T) + ">");
    }
}

Augmentation de la sécurité du type

Ce qui est vraiment génial à propos de l'utilisation de dynamic objet dynamic en remplacement de l'API de réflexion, c'est que vous ne perdez que la vérification du temps de compilation de ce type particulier que vous ne connaissez pas avant l'exécution. Les autres arguments et le nom de la méthode sont analysés de manière statique par le compilateur comme d'habitude. Si vous supprimez ou ajoutez d'autres arguments, changez leur type ou renommez le nom de la méthode, vous obtiendrez une erreur de compilation. Cela ne se produira pas si vous fournissez le nom de la méthode sous la forme d'une chaîne dans Type.GetMethod et les arguments sous la forme du tableau d'objets dans MethodInfo.Invoke .

Voici un exemple simple qui illustre comment certaines erreurs peuvent être détectées au moment de la compilation (code commenté) et d'autres lors de l'exécution. Il montre également comment le DLR essaie de résoudre la méthode à appeler.

interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }

class Program
{
    static void Main(string[] args)
    {
        var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
        for (int i = 0; i < objects.Length; i++)
        {
            ProcessItem((dynamic)objects[i], "test" + i, i);

            //ProcesItm((dynamic)objects[i], "test" + i, i);
            //compiler error: The name 'ProcesItm' does not
            //exist in the current context

            //ProcessItem((dynamic)objects[i], "test" + i);
            //error: No overload for method 'ProcessItem' takes 2 arguments
        }
    }

    static string ProcessItem<T>(T item, string text, int number)
        where T : IItem
    {
        Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
                          typeof(T), text, number);
        return "OK";
    }
    static void ProcessItem(BarItem item, string text, int number)
    {
        Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
    }
}

Ici, nous exécutons à nouveau une méthode en convertissant l'argument en type dynamic . Seule la vérification du type du premier argument est reportée à l'exécution. Vous obtiendrez une erreur de compilation si le nom de la méthode que vous appelez n'existe pas ou si d'autres arguments ne sont pas valides (nombre d'arguments incorrect ou types incorrects).

Lorsque vous passez l'argument dynamic à une méthode, cet appel est lié récemment . La résolution de surcharge de méthode se produit au moment de l'exécution et essaie de choisir la meilleure surcharge. Donc, si vous ProcessItem méthode ProcessItem avec un objet de type BarItem vous appelez la méthode non générique, car elle correspond mieux à ce type. Cependant, vous obtiendrez une erreur d'exécution lorsque vous passez un argument du type Alpha car il n'existe aucune méthode capable de gérer cet objet (une méthode générique a la contrainte where T : IItem et Alpha classe Alpha where T : IItem pas cette interface). Mais c'est tout le problème. Le compilateur n'a pas d'informations que cet appel est valide. En tant que programmeur, vous le savez, et vous devez vous assurer que ce code fonctionne sans erreur.

Type de retour gotcha

Lorsque vous appelez une méthode non-vide avec un paramètre de type dynamique, son type de retour sera probablement aussi dynamic . Donc, si vous changez l'exemple précédent pour ce code:

var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

alors le type de l'objet résultat serait dynamic . C'est parce que le compilateur ne sait pas toujours quelle méthode sera appelée. Si vous connaissez le type de retour de l'appel de fonction, vous devez le convertir implicitement en le type requis afin que le reste du code soit saisi statiquement:

string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

Vous obtiendrez une erreur d'exécution si le type ne correspond pas.

En fait, si vous essayez d'obtenir la valeur de résultat dans l'exemple précédent, vous obtiendrez une erreur d'exécution dans l'itération de la deuxième boucle. C'est parce que vous avez essayé d'enregistrer la valeur de retour d'une fonction vide.




Ajoutant à la réponse d'Adrian Gallero :

L'appel d'une méthode générique à partir d'informations de type implique trois étapes.

TLDR: appeler une méthode générique connue avec un objet type peut être accompli par:

((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition()
    .MakeGenericMethod(typeof(string))
    .Invoke(this, null);

GenericMethod<object> est le nom de la méthode à appeler et tout type qui satisfait les contraintes génériques.

(Action) correspond à la signature de la méthode à appeler ie ( Func<string,string,int> ou Action<bool> )

L'étape 1 obtient le MethodInfo pour la définition de méthode générique

Méthode 1: utilisez GetMethod () ou GetMethods () avec les types appropriés ou les indicateurs de liaison.

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");

Méthode 2: créez un délégué, récupérez l'objet MethodInfo, puis appelez GetGenericMethodDefinition

De l'intérieur de la classe qui contient les méthodes:

MethodInfo method = ((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

De l'extérieur de la classe qui contient les méthodes:

MethodInfo method = ((Action)(new Sample())
    .GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)Sample.StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

En C #, le nom d'une méthode, c'est-à-dire "ToString" ou "GenericMethod" fait référence à un groupe de méthodes pouvant contenir une ou plusieurs méthodes. Jusqu'à ce que vous fournissiez les types des paramètres de la méthode, on ne sait pas de quelle méthode vous parlez.

((Action)GenericMethod<object>) fait référence au délégué pour une méthode spécifique. ((Func<string, int>)GenericMethod<object>) fait référence à une surcharge différente de GenericMethod

Méthode 3: Créer une expression lambda contenant une expression d'appel de méthode, obtenir l'objet MethodInfo, puis GetGenericMethodDefinition

MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
    (Sample v) => v.GenericMethod<object>()
    )).Body).Method.GetGenericMethodDefinition();

Cela se décompose en

Créer une expression lambda où le corps est un appel à la méthode désirée.

Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();

Extraire le corps et convertir en MethodCallExpression

MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;

Obtenir la définition de méthode générique à partir de la méthode

MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();

L'étape 2 appelle MakeGenericMethod pour créer une méthode générique avec le (s) type (s) approprié (s).

MethodInfo generic = method.MakeGenericMethod(myType);

L'étape 3 appelle la méthode avec les arguments appropriés.

generic.Invoke(this, null);



Juste un ajout à la réponse originale. Alors que cela va fonctionner:

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

Il est également un peu dangereux en ce que vous perdez la vérification à la GenericMethod de GenericMethod . Si vous effectuez ultérieurement un refactoring et que vous renommez GenericMethod , ce code ne sera pas remarqué et échouera au moment de l'exécution. De même, s'il y a un post-traitement de l'assembly (par exemple, obscurcissement ou suppression de méthodes / classes inutilisées), ce code peut également être rompu.

Donc, si vous connaissez la méthode à laquelle vous vous connectez au moment de la compilation, et cela n'est pas appelé des millions de fois, les frais généraux ne sont pas importants, je changerais ce code en:

Action<> GenMethod = GenericMethod<int>;  //change int by any base type 
                                          //accepted by GenericMethod
MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name);
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

Bien que pas très joli, vous avez ici une référence de GenericMethod à GenericMethod , et si vous refactorisez, supprimez ou faites quoi que ce soit avec GenericMethod , ce code continuera à fonctionner, ou au moins cassera au moment de la compilation (si par exemple vous supprimez GenericMethod ).

Une autre façon de faire la même chose serait de créer une nouvelle classe wrapper et de la créer via Activator . Je ne sais pas s'il y a un meilleur moyen.




C'est mon 2 cents basé sur la réponse de Grax , mais avec deux paramètres requis pour une méthode générique.

Supposons que votre méthode est définie comme suit dans une classe Helpers:

public class Helpers
{
    public static U ConvertCsvDataToCollection<U, T>(string csvData)
    where U : ObservableCollection<T>
    {
      //transform code here
    }
}

Dans mon cas, le type U est toujours une collection observable stockant un objet de type T.

Comme mes types sont prédéfinis, je crée d'abord les objets "factices" qui représentent la collection observable (U) et l'objet stocké dans celle-ci (T) et qui seront utilisés ci-dessous pour obtenir leur type lors de l'appel du Make

object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);

Puis appelez le GetMethod pour trouver votre fonction générique:

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

Jusqu'à présent, l'appel ci-dessus est à peu près identique à ce qui a été expliqué ci-dessus mais avec une petite différence lorsque vous devez passer plusieurs paramètres.

Vous devez passer un tableau Type [] à la fonction MakeGenericMethod qui contient les types d'objets "fictifs" qui ont été créés ci-dessus:

MethodInfo generic = method.MakeGenericMethod(
new Type[] {
   myCollection.GetType(),
   myObject.GetType()
});

Une fois cela fait, vous devez appeler la méthode Invoke comme mentionné ci-dessus.

generic.Invoke(null, new object[] { csvData });

Et tu as fini. Fonctionne un charme!

METTRE À JOUR:

Comme @Bevan l'a souligné, je n'ai pas besoin de créer un tableau lorsque j'appelle la fonction MakeGenericMethod comme elle le fait dans les paramètres et je n'ai pas besoin de créer un objet pour obtenir les types car je peux simplement passer les types directement à cette fonction. Dans mon cas, comme j'ai les types prédéfinis dans une autre classe, j'ai simplement changé mon code pour:

object myCollection = null;

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

MethodInfo generic = method.MakeGenericMethod(
   myClassInfo.CollectionType,
   myClassInfo.ObjectType
);

myCollection = generic.Invoke(null, new object[] { csvData });

myClassInfo contient 2 propriétés de type Type que j'ai définies à l'exécution en fonction d'une valeur enum passée au constructeur et me fourniront les types pertinents que j'utiliserai ensuite dans MakeGenericMethod.

Merci encore de mettre en évidence ce @Bevan.




It depends on the context

When you consider your team as a list of players, you are projecting the "idea" of a foot ball team down to one aspect: You reduce the "team" to the people you see on the field. This projection is only correct in a certain context. In a different context, this might be completely wrong. Imagine you want to become a sponsor of the team. So you have to talk to the managers of the team. In this context the team is projected to the list of its managers. And these two lists usually don't overlap very much. Other contexts are the current versus the former players, etc.

Unclear semantics

So the problem with considering a team as a list of its players is that its semantic depends on the context and that it cannot be extended when the context changes. Additionally it is hard to express, which context you are using.

Classes are extensible

When you using a class with only one member (eg IList activePlayers ), you can use the name of the member (and additionally its comment) to make the context clear. When there are additional contexts, you just add an additional member.

Classes are more complex

In some cases it might be overkill to create a extra class. Each class definition must be loaded through the classloader and will be cached by the virtual machine. This costs you runtime performance and memory. When you have a very specific context it might be OK to consider a football team as a list of players. But in this case, you should really just use a IList , not a class derived from it.

Conclusion / Considerations

When you have a very specific context, it is OK to consider a team as a list of players. For example inside a method it is completely OK to write

IList<Player> footballTeam = ...

When using F#, it can even be OK to create a type abbreviation

type FootballTeam = IList<Player>

But when the context is broader or even unclear, you should not do this. This is especially the case, when you create a new class, where it is not clear in which context it may be used in the future. A warning sign is when you start to add additional attributes to your class (name of the team, coach, etc.). This is a clear sign that the context where the class will be used is not fixed and will change in the future. In this case you cannot consider the team as a list of players, but you should model the list of the (currently active, not injured, etc.) players as an attribute of the team.







c# generics reflection