Courte présentation (une petite approche) des techniques qui permettent la programmation dynamique : réflexion, génériques, arborescence d'expression, LINQ, création d'IL, compilation dynamique, DLR.
Bonne lecture...
Pour rappel : vous pouvez retourner le type (System.Type) d'un objet avec sa méthode GetType() (héritage de object) et d'un type avec le mot clef typeof. Partant de là, il est possible de connaître toutes les composantes de l'objet et de son type et d'utiliser ses méthodes -hors discussion sur les permissions-. Il existe d'autres mots clefs (is, as) et classes pour compléter l'utilisation. On peut ainsi `parcourir' et utiliser tout son module...
Exemple : utilisation d'une méthode connue d'une classe 'inconnue' contenant cette méthode
public void Add<T1, T2>(T1 liste, T2 item) { liste.GetType().GetMethod("Add").Invoke(liste, new object[] { item }); }
C'est un nouveau type du prochain Framework 4 permettant de passer la compilation sans vérification (donc pas d'IntelliSense). Le type réel sera défini et vérifié dynamiquement par réflexion
Ex : dynamic objet = ..un objet, par exemple un List<string>..; objet.Add("string1");
De même, avec ScriptObject. Ex : ScriptObject liste = ..un objet, par exemple un List<string>..; liste.Invoke("Add", "string1");
Il simule dynamiquement un objet (pouvant être distant) en interceptant les appels sur cet objet. Pour l'utiliser il faut dériver de la classe RealProxy (classe abstract de System.Runtime.Remoting.Proxies) et implémenter la méthode Invoke qui sera appelée pour chaque appel sur l'objet derrière le proxy.
Les délégués génériques :
- Func<..TypesParamètres.., TypeDeRetour> eq. à delegate TypeDeRetour Func(..TypesParamètres..);
- Action<..TypesParamètres..> eq. à delegate void Func(..TypesParamètres..);
Les classes génériques, ex :
public void CréationListGen<T>(T objet) { var typeListeGen = typeof(List<>); // Le CLR va définir dynamiquement le type générique (ex string). Equivaut ici à List<T> var typeListeDeStrings = typeListeGen.MakeGenericType(typeof(T)); // Création d'une liste, juste pour l'exemple var listeDeStrings = typeListeDeStrings.GetConstructor(Type.EmptyTypes).Invoke(null); // Ajout d'un élément, juste pour l'exemple listeDeStrings.GetType().GetMethod("Add").Invoke(listeDeStrings, new object[] { objet }); }
La classe Expression contient des méthodes de fabrique statiques qui créent des noeuds d'arborescence d'expression. L'espace de noms System.Linq.Expressions fournit une API pour générer des arborescences d'expression manuellement. On peut également fournir un délégué qui sera -si possible- décomposé en arbre. Cet arbre peut être parcouru, modifié et compilé dynamiquement. Ex :
// Expression lambda décomposée sous forme d'arbre. Expression<Func<double, double>> expr = x => Math.Pow(x, 2) + 3 * x + 5; // L'arbre est compilé en code qui pourra être exécuté par appel de delExpr. Func<double, double> delExpr = expr.Compile(); double val = delExpr(5.0);
En effet les requêtes (notamment avec Linq to SQL) sont repoussées et exécutées uniquement au moment de leur utilisation. Pour ce faire Linq utilise abondamment les génériques, les expressions lambda et les arborescences d'expression.
Note : on peut - pour améliorer les performances - compiler ses requêtes. Ex :
System.Data.Linq.CompiledQuery.Compile((source, type de paramètres optionnels) => requête linq);
Dans System.Reflection.Emit plusieurs classes (DynamicMethod, etc) permettent de construire du code IL (Intermediate Language) qui peut être interprété dynamiquement.
Ex :
// Type des paramètres Type[] helloArgs = { typeof(string), typeof(int) }; // Méthode crée dynamiquement DynamicMethod hello = new DynamicMethod("Hello", // son nom typeof(int), // type du retour helloArgs, // type des paramètres typeof(string).Module); // On prépare l'injection du code : sortie sur la Console... Type[] writeStringArgs = { typeof(string) }; MethodInfo writeString = typeof(Console).GetMethod("WriteLine", writeStringArgs); ILGenerator il = hello.GetILGenerator(256); // Injection du code... il.Emit(OpCodes.Ldarg_0); il.EmitCall(OpCodes.Call, writeString, null); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ret); // Infos sur les paramètres ; pour le debugging ParameterBuilder parameter1 = hello.DefineParameter(1, ParameterAttributes.In, "message"); ParameterBuilder parameter2 = hello.DefineParameter(2, ParameterAttributes.In, "valueToReturn"); // Un delegate pour exécuter la méthode dynamique. HelloDelegate hi = (HelloDelegate)hello.CreateDelegate(typeof(HelloDelegate)); // avec delegate int HelloDelegate(string msg, int ret); int retval = hi("rnHello, World!", 42);
Les classes dans l'espace Microsoft.CSharp (et System.CodeDom.Compiler) permettent de compiler (en mémoire ou dans un fichier) et le cas échéant d'exécuter du code contenu dans un string.
Ex :
CSharpCodeProvider provider = new CSharpCodeProvider(); ICodeCompiler compileur = provider.CreateCompiler(); CompilerResults cr = provider.CompileAssemblyFromSource(new CompilerParameters(), @"using System; public class Eval{public static double MultPar2(double x) { return x * 2; } }");// Le code. if (cr.Errors.Count > 0) throw new Exception("Erreur sur le code dynamique."); var ret = cr.CompiledAssembly.GetType("Eval").GetMethod("MultPar2").Invoke(null, new object[] { 50 });// Retourne 100.
La Dynamic Language Runtime est une surcouche du .NET Framework 3.5 permettant de faciliter la création de langages dynamiques pour .NET. Ce langage peut être un langage intégré à une application ou plus largement l'implémentation d'un nouveau langage pour .NET à l'image d'IronPython ou de IronRuby. Son utilisation est facilitée avec Silverlight notamment avec le Javascript et/ou Python.
Source en vidéo : http://www.microsoft.com/france/vision/mstechdays09/Webcast.aspx?eID=490a4112-6a60-403d-9e86-9ada349529b1