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:
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 GetType()
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 la Process
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:
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 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:
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) + ">");
}
}
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îne Type.GetMethod
et les arguments sous forme de tableau d'objets 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 au moment 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 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 la ProcessItem
méthode avec un objet de BarItem
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 du Alpha
type car aucune méthode ne peut gérer cet objet (une méthode générique a la contrainte where T : IItem
et la Alpha
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:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
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:
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 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.