web-dev-qa-db-fra.com

Génériques en C #, utilisant le type d'une variable comme paramètre

J'ai une méthode générique

bool DoesEntityExist<T>(Guid guid, ITransaction transaction) where T : IGloballyIdentifiable;

Comment utiliser la méthode de la manière suivante:

Type t = entity.GetType();
DoesEntityExist<t>(entityGuid, transaction);

Je continue à recevoir l'erreur de compilation suivante:

Le type ou le nom de l'espace de noms 't' est introuvable (il manque une directive using ou une référence Assembly?)

DoesEntityExist<MyType>(entityGuid, transaction);

fonctionne parfaitement mais je ne veux pas utiliser une directive if pour appeler la méthode avec un nom de type séparé à chaque fois.

118
Germstorm

Le but des génériques est de donner au moment de la compilation la sécurité du type - ce qui signifie que les types doivent être connus au moment de la compilation.

Vous pouvez appeler des méthodes génériques avec des types uniquement connus au moment de l'exécution, mais vous devez utiliser la réflexion:

// For non-public methods, you'll need to specify binding flags too
MethodInfo method = GetType().GetMethod("DoesEntityExist")
                             .MakeGenericMethod(new Type[] { t });
method.Invoke(this, new object[] { entityGuid, transaction });

Ick.

Pouvez-vous rendre votre méthode appelante générique et transmettre votre paramètre de type comme argument de type, en poussant la décision d'un niveau plus haut dans la pile?

Si vous pouviez nous donner plus d'informations sur ce que vous faites, cela aiderait. Parfois, vous devrez peut-être utiliser la réflexion comme ci-dessus, mais si vous choisissez le bon point pour le faire, vous pouvez vous assurer que vous n’avez besoin de le faire qu’une seule fois, et laissez tout ce qui se trouve en dessous de ce point utiliser le paramètre type de manière normale.

159
Jon Skeet

Une façon de contourner ce problème consiste à utiliser le casting implicite:

bool DoesEntityExist<T>(T entity, Guid guid, ITransaction transaction) where T : IGloballyIdentifiable;

en l'appelant comme ça:

DoesEntityExist(entity, entityGuid, transaction);

Pour aller plus loin, vous pouvez le transformer en une méthode d'extension (il faudra le déclarer dans une classe statique):

static bool DoesEntityExist<T>(this T entity, Guid guid, ITransaction transaction) where T : IGloballyIdentifiable;

appelant ainsi:

entity.DoesEntityExist(entityGuid, transaction);
33
Joe Lloyd

Je ne suis pas sûr d'avoir bien compris votre question, mais vous pouvez écrire votre code de cette façon:

bool DoesEntityExist<T>(T instance, ....)

Vous pouvez appeler la méthode de la manière suivante:

DoesEntityExist(myTypeInstance, ...)

De cette manière, vous n'avez pas besoin d'écrire explicitement le type, le framework le dépassera automatiquement de l'instance.

7
kosto

Vous ne pouvez pas l'utiliser de la manière que vous décrivez. Le problème des types génériques est que, bien que vous ne puissiez pas les connaître au "moment du codage", le compilateur doit pouvoir les résoudre au moment de la compilation. Pourquoi? Parce que sous le capot, le compilateur s'en va et crée un nouveau type (parfois appelé type générique fermé) pour chaque utilisation différente du type générique "ouvert".

En d'autres termes, après la compilation,

DoesEntityExist<int>

est un type différent de

DoesEntityExist<string>

C’est ainsi que le compilateur est capable de garantir la sécurité des types au moment de la compilation.

Pour le scénario que vous décrivez, vous devez passer le type sous forme d'argument pouvant être examiné au moment de l'exécution.

L’autre option, comme mentionné dans d’autres réponses, consiste à utiliser la réflexion pour créer le type fermé à partir du type ouvert, bien que cela soit probablement recommandé dans tout scénario autre que celui de niche extrême.

4
Rob Levine