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 à l'exécution?
Considérez l'exemple de code suivant - à l'intérieur de la Example()
méthode, quelle est la façon la plus concise d'invoquer en GenericMethod<T>()
utilisant le Type
stocké dans la myType
variable?
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>()
{
//...
}
}
c#
.net
generics
reflection
Bevan
la source
la source
BindingFlags.Instance
, et pas seulementBindingFlags.NonPublic
, obtenir la méthode privée / interne.Réponses:
Vous devez utiliser la réflexion pour démarrer la méthode, puis la "construire" en fournissant des arguments de type avec MakeGenericMethod :
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é, cela est beaucoup plus simple à partir de C # 4
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.la source
GetMethod()
prend en compte que les méthodes d'instance publiques par défaut, vous devrez peut - êtreBindingFlags.Static
et / ouBindingFlags.NonPublic
.BindingFlags.NonPublic | BindingFlags.Instance
(et éventuellementBindingFlags.Static
).dynamic
n'aide pas car l'inférence de type n'est pas disponible. (Il n'y a aucun argument que le compilateur peut utiliser pour déterminer l'argument type.)Juste un ajout à la réponse originale. Bien que cela fonctionne:
Il est également un peu dangereux dans la mesure où vous perdez la vérification au moment de la compilation
GenericMethod
. Si vous effectuez plus tard une refactorisation et renommezGenericMethod
, ce code ne remarquera pas et échouera au moment de l'exécution. En outre, s'il y a un post-traitement de l'assembly (par exemple, obscurcir ou supprimer des méthodes / classes inutilisées), ce code peut également casser.Donc, si vous connaissez la méthode à laquelle vous vous connectez au moment de la compilation, et que cela n'est pas appelé des millions de fois, la surcharge n'a donc pas d'importance, je changerais ce code comme suit:
Bien qu'il ne soit pas très joli, vous disposez d'une référence de temps de compilation
GenericMethod
ici, et si vous refactorisez, supprimez ou faites quoi que ce soitGenericMethod
, ce code continuera à fonctionner, ou au moins se cassera au moment de la compilation (si, par exemple, vous supprimezGenericMethod
).Une autre façon de faire de même serait de créer une nouvelle classe wrapper et de la créer via
Activator
. Je ne sais pas s'il y a une meilleure façon.la source
GenMethod.Method.GetGenericMethodDefinition()
au lieu dethis.GetType().GetMethod(GenMethod.Method.Name)
. Il est légèrement plus propre et probablement plus sûr.nameof(GenericMethod)
L'appel d'une méthode générique avec un paramètre de type connu uniquement au moment de l'exécution peut être grandement simplifié en utilisant un
dynamic
type au lieu de l'API de réflexion.Pour utiliser cette technique, le type doit être connu à partir de l'objet réel (pas seulement une instance de la
Type
classe). 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 à l'aide de l' activateur. méthode .Si vous voulez appeler une méthode générique, qui en utilisation "normale" aurait eu son type déduit, alors il s'agit simplement de transtyper l'objet de type inconnu en
dynamic
. Voici un exemple:Et voici la sortie de ce programme:
Process
est une méthode d'instance générique qui écrit le type réel de l'argument passé (en utilisant laGetType()
méthode) et le type du paramètre générique (en utilisant l'typeof
opérateur).En convertissant l'argument objet en
dynamic
type, nous avons différé la fourniture du paramètre type jusqu'à l'exécution. Lorsque laProcess
méthode est appelée avec ledynamic
argument, 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 vrais types 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, elle est donc invoquée avec un paramètre de type approprié.Dans cet exemple, la sortie est la même que si vous écriviez:
La version avec un type dynamique est nettement plus courte et plus facile à écrire. Vous ne devez pas non plus vous soucier des performances de l'appel de cette fonction plusieurs fois. Le prochain appel avec des arguments du même type devrait être plus rapide grâce au mécanisme de mise en cache dans DLR. Bien sûr, vous pouvez écrire du code qui met en cache les délégués invoqués, mais en utilisant le
dynamic
type vous obtenez ce comportement gratuitement.Si la méthode générique que vous souhaitez appeler n'a pas d'argument de type paramétré (de sorte que son paramètre de type ne peut pas être déduit), vous pouvez encapsuler l'invocation de la méthode générique dans une méthode d'assistance comme dans l'exemple suivant:
Sécurité de type accrue
Ce qui est vraiment génial à propos de l'utilisation de l'
dynamic
objet en remplacement de l'utilisation 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 jusqu'à l'exécution. Les autres arguments et le nom de la méthode sont analysés statiquement par le compilateur comme d'habitude. Si vous supprimez ou ajoutez des arguments, modifiez leurs types ou renommez le nom de la méthode, vous obtiendrez une erreur au moment de la compilation. Cela ne se produira pas si vous fournissez le nom de la méthode sous forme de chaîneType.GetMethod
et les arguments sous forme de tableau d'objetsMethodInfo.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 au moment de l'exécution. Il montre également comment le DLR essaie de résoudre la méthode à appeler.
Ici, nous exécutons à nouveau une méthode en convertissant l'argument en
dynamic
type. 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 (mauvais nombre d'arguments ou mauvais types).Lorsque vous passez l'
dynamic
argument à une méthode, cet appel est lié récemment . La résolution de surcharge de méthode se produit lors de l'exécution et essaie de choisir la meilleure surcharge. Donc, si vous appelez laProcessItem
méthode avec un objet deBarItem
type, vous appellerez alors la méthode non générique, car elle correspond mieux à ce type. Cependant, vous obtiendrez une erreur d'exécution lorsque vous passerez un argument duAlpha
type car aucune méthode ne peut gérer cet objet (une méthode générique a la contraintewhere T : IItem
et laAlpha
classe n'implémente pas cette interface). Mais c'est tout. Le compilateur ne dispose pas d'informations sur la validité de cet appel. En tant que programmeur, vous le savez et vous devez vous assurer que ce code s'exécute 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 être
dynamic
trop . Donc, si vous changez l'exemple précédent en ce code:alors le type de l'objet résultat serait
dynamic
. En effet, 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 au type requis pour que le reste du code soit typé statiquement: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 la deuxième itération de boucle. Cela est dû au fait que vous avez essayé d'enregistrer la valeur de retour d'une fonction void.
la source
ProcessItem
méthode générique a une contrainte générique et n'accepte que les objets qui implémentent l'IItem
interface. Lorsque vous appellerezProcessItem(new Aplha(), "test" , 1);
ouProcessItem((object)(new Aplha()), "test" , 1);
vous obtiendrez une erreur du compilateur, mais lors de la conversion,dynamic
vous reporter cette vérification à l'exécution.Avec C # 4.0, la réflexion n'est pas nécessaire car le DLR peut l'appeler à l'aide de types d'exécution. Étant donné que l'utilisation de la bibliothèque DLR est une sorte de douleur dynamiquement (au lieu du compilateur C # générant du code pour vous), le framework open source Dynamitey (.net standard 1.5) vous donne un accès d'exécution en cache facile aux mêmes appels que le compilateur générerait pour vous.
la source
Pour compléter la réponse d'Adrian Gallero :
L'appel d'une méthode générique à partir de type info implique trois étapes.
TLDR: L'appel d'une méthode générique connue avec un objet type peut être accompli par:
où
GenericMethod<object>
est le nom de la méthode à appeler et tout type qui satisfait aux contraintes génériques.(Action) correspond à la signature de la méthode à appeler, c'est-à-dire (
Func<string,string,int>
ouAction<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.
Méthode 2: créez un délégué, obtenez l'objet MethodInfo, puis appelez GetGenericMethodDefinition
De l'intérieur de la classe qui contient les méthodes:
De l'extérieur de la classe qui contient les méthodes:
En C #, le nom d'une méthode, c'est-à-dire "ToString" ou "GenericMethod" fait en fait référence à un groupe de méthodes qui peut contenir une ou plusieurs méthodes. Jusqu'à ce que vous fournissiez les types des paramètres de méthode, on ne sait pas à quelle méthode vous faites référence.
((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 GenericMethodMéthode 3: créer une expression lambda contenant une expression d'appel de méthode, obtenir l'objet MethodInfo, puis GetGenericMethodDefinition
Cela se décompose en
Créez une expression lambda où le corps est un appel à la méthode souhaitée.
Extraire le corps et transtyper en MethodCallExpression
Obtenez la définition de méthode générique à partir de la méthode
L'étape 2 appelle MakeGenericMethod pour créer une méthode générique avec le ou les types appropriés.
L'étape 3 appelle la méthode avec les arguments appropriés.
la source
Personne n'a fourni la solution " Reflection classique ", voici donc un exemple de code complet:
La
DynamicDictionaryFactory
classe ci - dessus a une méthodeCreateDynamicGenericInstance(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 lors de l'appel
keyType
etvalueType
.Voici un exemple complet comment appeler cette méthode pour instancier et utiliser un
Dictionary<String, int>
:Lorsque l'application console ci-dessus est exécutée, nous obtenons le résultat attendu correct:
la source
Ceci 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 soit définie comme suit dans une classe Helpers:
Dans mon cas, le type U est toujours une collection observable stockant un objet de type T.
Comme j'ai mes types prédéfinis, je crée d'abord les objets "factices" qui représentent la collection observable (U) et l'objet qui y est stocké (T) et qui seront utilisés ci-dessous pour obtenir leur type lors de l'appel de Make
Appelez ensuite GetMethod pour trouver votre fonction générique:
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 lui passer plusieurs paramètres.
Vous devez passer un tableau Type [] à la fonction MakeGenericMethod qui contient les types d'objets "factices" qui ont été créés ci-dessus:
Une fois cela fait, vous devez appeler la méthode Invoke comme mentionné ci-dessus.
Et tu as fini. Fonctionne un charme!
MISE À JOUR:
Comme @Bevan l'a souligné, je n'ai pas besoin de créer un tableau lors de l'appel de la fonction MakeGenericMethod car elle prend des 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 en:
myClassInfo contient 2 propriétés de type
Type
que j'ai définies au moment de l'exécution en fonction d'une valeur d'énumération transmise au constructeur et me fournira les types pertinents que j'utilise ensuite dans MakeGenericMethod.Merci encore d'avoir souligné ce @Bevan.
la source
MakeGenericMethod()
avoir le mot-clé params afin que vous n'ayez pas besoin de créer un tableau; vous n'avez pas non plus besoin de créer d'instances pour obtenir les types -methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))
serait suffisant.Inspiré par la réponse d' Enigmativity - supposons que vous avez deux (ou plus) classes, comme
et vous voulez appeler la méthode
Foo<T>
avecBar
etSquare
, qui est déclarée commeEnsuite, vous pouvez implémenter une méthode d'extension comme:
Avec cela, vous pouvez simplement invoquer
Foo
comme:qui fonctionne pour chaque classe. Dans ce cas, il affichera:
la source