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 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 Type
). Sinon, vous devez créer un objet de ce type ou utiliser l'API de réflexion standard. solution . Vous pouvez créer un objet en utilisant Activator.CreateInstance méthode.
Si vous voulez appeler une méthode générique dont le type aurait été déduit dans une utilisation "normale", il suffit de couler un objet de type inconnu dans la catégorie 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 fonction GetType()
) et le type de paramètre générique (en utilisant la méthode typeof
opérateur).
En transformant l'argument de l'objet en dynamic
nous avons reporté la fourniture du paramètre de type jusqu'au moment de l'exécution. Lorsque le Process
est appelée avec dynamic
alors le compilateur ne se soucie pas du type de cet argument. Le compilateur génère du code qui, à l'exécution, vérifie les types réels des arguments passés (en utilisant la réflexion) et choisit la meilleure méthode à appeler. Ici, il n'y a que cette seule méthode générique, elle est donc invoquée avec un paramètre de type approprié.
Dans cet exemple, le résultat est le même que si vous aviez écrit :
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 le type dynamique est définitivement plus courte et plus facile à écrire. Vous ne devez pas non plus vous inquiéter des performances de l'appel multiple de cette fonction. L'appel suivant avec des arguments du même type devrait être plus rapide grâce à la fonction mise en cache dans le DLR. Bien sûr, vous pouvez écrire du code qui met en cache les délégués invoqués, mais en utilisant le mécanisme de la dynamic
type 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 d'aide 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é accrue du type
Ce qui est vraiment génial dans l'utilisation dynamic
en remplacement de l'utilisation de l'API de réflexion est que vous ne perdez que la vérification au moment de la compilation de ce type particulier que vous ne connaissez pas jusqu'au moment de 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, 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 en tant que chaîne dans le champ Type.GetMethod
et les arguments sous forme de 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 au moment de l'exécution. Il montre également comment DLR tente de résoudre la question de la méthode à appeler.
interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
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 de nouveau une méthode en coulant l'argument en dynamic
type. Seule la vérification du type du premier argument est reportée au moment de l'exécution. Vous obtiendrez une erreur de compilation si le nom de la méthode que vous appelez n'existe pas ou si les autres arguments sont invalides (nombre d'arguments ou types incorrects).
Quand vous passez dynamic
à la méthode, alors cet appel est dernièrement lié . La résolution des surcharges des méthodes se fait au moment de l'exécution et tente de choisir la meilleure surcharge. Ainsi, si vous invoquez ProcessItem
avec un objet de BarItem
alors vous ferez appel à une méthode non générique car elle correspond mieux à ce type. Cependant, vous obtiendrez une erreur d'exécution si vous passez un argument de type Alpha
parce qu'il n'y a aucune méthode qui peut gérer cet objet. Mais c'est là tout le problème. Le compilateur n'a pas l'information que cet appel est valide. 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 par ce code :
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
alors le type d'objet résultat serait dynamic
. Ceci est dû au fait que le compilateur ne sait pas toujours quelle méthode sera appelée. Si vous connaissez le type de retour de l'appel de la fonction, vous devez alors convertir implicitement en type requis pour que le reste du code soit typé statique :
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 du résultat dans l'exemple précédent, vous obtiendrez une erreur d'exécution à la deuxième itération de la boucle. Ceci est dû au fait que vous avez essayé de sauvegarder la valeur de retour d'une fonction void.