Le mot clé dynamique et le DLR (Dynamic Language Runtime) sont de nouvelles fonctionnalités majeures de C# 4 et de Microsoft .NET Framework 4. Ces fonctionnalités ont suscité beaucoup d'intérêt lorsqu'elles ont été annoncées, ainsi que beaucoup de questions. Il y a eu beaucoup de réponses cependant, mais elles sont réparties dans différentes documentations et dans des articles et des blogs techniques. C'est pourquoi les gens continuent de poser les mêmes questions encore et encore dans les forums et lors de conférences.
Cet article propose une présentation générale des fonctionnalités dynamiques de C# 4 et donne également des informations détaillées sur leur fonctionnement avec d'autres langages et fonctionnalités de l'environnement telles que la réflexion ou les variables typées implicitement. Étant donné qu'il y a déjà beaucoup d'informations disponibles, je vais quelquefois réutiliser des exemples classiques avec leurs liens vers leur source d'origine. Je vais également proposer beaucoup de liens pour plus d'informations.
Les langages de programmation sont parfois divisés en langages statiquement typés et en langages dynamiquement typés. C# et Java sont souvent considérés comme des exemples de langages statiquement typés, tandis que Python, Ruby et JavaScript sont des exemples de langages dynamiquement.
En général, les langages dynamiques ne réalisent pas de vérification des types au moment de la compilation et identifient les types d'objets uniquement au moment de l'exécution. Cette approche a ses avantages et ses inconvénients : Souvent, le code s'écrit plus rapidement et plus facilement, mais en même temps, vous n'obtenez pas les erreurs de compilateur et vous devez utiliser le test unitaire et d'autres techniques pour assurer le comportement normal de votre application.
À l'origine, C# 4 a été créé comme un langage purement statique, mais avec C# 4, des éléments dynamiques ont été ajoutés pour améliorer l'interopérabilité avec des langages et des environnements dynamiques. L'équipe C# a envisagé plusieurs types de conception, mais elle a finalement décidé d'ajouter un nouveau mot clé pour prendre en charge ces fonctionnalités : le mot clé dynamique.
Le mot clé dynamique agit comme une déclaration de type statique dans le système de type C#. De cette façon, C# a des fonctionnalités dynamiques tout en restant un langage statiquement typé. Les raisons de cette décision et la façon dont elle a été prise sont expliquées dans la présentation « Dynamic Binding in C#4 » (Liaison dynamique en C# 4) par Mads Torgersen à PDC09 (microsoftpdc.com/2009/FT31). Entre autres choses, il a été décidé que les objets dynamiques devraient être des éléments prioritaires du langage C#, de sorte qu'il n'y a pas de possibilité d'activer ou de désactiver les fonctionnalités dynamiques et rien de similaire à l'Option Strict On/Off de Visual Basic n'a été ajouté à C#.
Lorsque vous utilisez le mot clé dynamique, vous dites au compilateur d'arrêter la vérification lors de la compilation. Il y a plusieurs exemples sur le Web et dans la documentation MSDN (msdn.microsoft.com/library/dd264736) sur la façon d'utiliser ce mot clé. En voici un exemple courant :
dynamic d = "test";
Console.WriteLine(d.GetType());
// Prints "System.String".
d = 100;
Console.WriteLine(d.GetType());
// Prints "System.Int32".Comme vous pouvez le voir, il est possible d'affecter des objets de différents types à une variable déclarée dynamique. Le code se compile et le type d'objet est identifié au moment de l'exécution. Cependant, ce code se compile, mais il génère également une exception pendant l'exécution :
dynamic d = "test";
// The following line throws an exception at run time.
d++;La raison est la même : le compilateur ne reconnaît pas le type d'exécution de l'objet et ne peut donc pas vous dire que l'opération d'incrémentation n'est pas prise en charge dans ce cas.
L'absence de vérification de type lors de la compilation provoque également l'absence d'IntelliSense. Comme le compilateur C# ne reconnaît pas le type de l'objet, il ne peut énumérer ses propriétés et ses méthodes. Ce problème peut être résolu avec une inférence de type supplémentaire, comme c'est le cas avec les outils IronPython pour Visual Studio, mais pour l'instant, C# ne le fournit pas.
Néanmoins, dans plusieurs scénarios qui auraient pu tirer partie des fonctionnalités dynamiques, IntelliSense n'était pas disponible parce que le code utilisait des littéraux de chaîne. Cette question est abordée de façon plus détaillée plus loin dans cet article.
Quelle est donc la vraie différence entre dynamique, objet et var et quand faut-il les utiliser ? Voici de brèves définitions de chaque mot clé et quelques exemples.
Le mot clé objet représente le type System.Object qui est le type de racine dans la hiérarchie de classe C# 4. Ce mot clé est souvent utilisé lorsqu'il n'est pas possible d'identifier le type d'objet lors de la compilation, ce qui arrive souvent dans différents scénarios d'interopérabilité.
Vous devez utiliser des conversions explicites pour convertir une variable déclarée comme objet en un type spécifique.
object objExample = 10;
Console.WriteLine(objExample.GetType());Cela imprime visiblement System.Int32. Cependant, le type statique est System.Objet. Il faut donc une conversion explicite à ce niveau :
objExample = (int)objExample + 10;Vous pouvez affecter des valeurs de différents types parce qu'elles héritent toutes de System.Object :
objExample = "test";Depuis C# 3.0, le mot clé var est utilisé pour des variables locales typées de façon implicite et pour des types anonymes. Ce mot clé est généralement utilisé avec LINQ. Lorsqu'une variable est déclarée par l'utilisation du mot clé var, le type de variable est déduit de la chaîne d'initialisation pendant la compilation. Le type de la variable ne peut être modifié au moment de l'exécution. Si le compilateur ne peut déduire le type, il génère une erreur de compilation :
var varExample = 10;
Console.WriteLine(varExample.GetType());Comme pour le type statique, System.Int32 est affiché.
Dans l'exemple suivant, aucune conversion n'est requise parce que le type statique de varExample est System.Int32 :
varExample = varExample + 10;Cette ligne ne se compile pas parce que vous pouvez seulement attribuer deux nombres entiers à varExample :
varExample = "test";Le mot clé dynamique, introduit dans C# 4, rend certains scénarios dépendant habituellement du mot clé objet plus faciles à écrire et à entretenir. En fait, le type dynamique utilise le type System.Object en arrière-plan, mais contrairement à l'objet, il ne requiert pas d'opérations de conversions explicites au moment de la compilation, parce qu'il n'identifie le type qu'au moment de l'exécution :
dynamic dynamicExample = 10;
Console.WriteLine(dynamicExample.GetType());System.Int32 est affiché.
Dans la ligne suivante, aucune conversion n'est requise, parce que le type n'est identifié qu'au moment de l'exécution :
dynamicExample = dynamicExample + 10;Vous pouvez attribuer des valeurs différentes à dynamicExample :
dynamicExample = "test";Un billet de blog détaillé sur les différences entre les mots clés objet et dynamique est disponible sur le blog Foire aux questions C# (bit.ly/c95hpl).
Ce qui crée de la confusion parfois, c'est que tous ces mots clés peuvent être utilisés ensemble ; ils ne sont pas mutuellement exclusifs. Examinons ce code, par exemple :
dynamic dynamicObject = new Object();
var anotherObject = dynamicObject;Quel est le type d'anotherObject ? La réponse est : dynamique. Rappelez-vous que dynamique est en fait un type statique dans le système de type C#, de sorte que le compilateur affecte ce type à anotherObject. Il est important de comprendre que le mot clé var n'est qu'une instruction donnée au compilateur pour qu'il déduise le type à partir de l'expression d'initialisation de la variable ; var n'étant pas un type.
Lorsque vous entendez le terme « dynamique » en référence au langage C#, c'est généralement par rapport à un ou deux concepts : le mot clé dynamique dans C# 4 ou le DLR. Bien que ces deux concepts soient liés, il est important de comprendre leur différence.
Le DLR a deux objectifs principaux. D'abord, elle permet l'interopérabilité entre des langages dynamiques et le .NET Framework. Ensuite, elle apporte un comportement dynamique à C# et Visual Basic.
Le DLR a été créée à partir des leçons apprises au moment de la création d'IronPython (ironpython.net), qui a été le premier langage dynamique implémenté sur le .NET Framework. En travaillant sur IronPython, l'équipe a découvert qu'elle pouvait réutiliser son implémentation pour d'autres langages et a ainsi créé une plateforme commune sous-jacente pour les langages dynamiques du .NET. Tout comme IronPython, le DLR est devenue un projet open source et son code source est désormais disponible à l'adresse dlr.codeplex.com.
Plus tard, le DLR a également été incluse dans le .NET Framework 4 pour prendre en charge des fonctionnalités en C# et en Visual Basic. Si vous n'avez besoin que du mot clé dynamique en C# 4, vous pouvez simplement utiliser le .NET Framework et dans la plupart des cas, il gérera de manière autonome toutes les interactions avec le DLR. Mais si vous voulez implémenter ou intégrer un nouveau langage dynamique sur le .NET, vous pouvez bénéficier de classes supplémentaires dans le projet open source, qui a plus de fonctionnalités et de services pour les implémenteurs de langage.
Il n'est pas question d'utiliser le mot clé Dynamic à chaque fois que c'est possible à la place des déclarations de type statique. La vérification au moment de la compilation est un instrument puissant et plus vous pouvez l'utiliser, mieux c'est. Et encore une fois, les objets dynamiques dans C# ne prennent pas en charge IntelliSense, ce qui peut avoir un certain impact sur la productivité générale.
En même temps, il y a des scénarios qui sont difficiles à implémenter dans C# 4 avant le mot clé Dynamic et le DLR. Dans la plupart des cas, ils utilisaient le type System.object et une conversion explicite et ne pouvaient pas de toute façon bénéficier des avantages de la vérification lors de la compilation et d'IntelliSense. Voici quelques exemples.
Le scénario le plus connu est quand vous devez utiliser le mot clé objet pour une interopérabilité avec d'autres langages ou environnements. D'habitude, vous devez vous appuyer sur la réflexion pour obtenir le type d'objet et pour accéder à ses propriétés et méthodes. La syntaxe est parfois difficile à lire et par conséquent le code est difficile à entretenir. L'utilisation de dynamique ici peut être plus facile et plus commode que la réflexion.
Anders Hejlsberg en a donné un bon exemple à PDC08 (channel9.msdn.com/pdc2008/TL16) qui ressemble à ceci :
object calc = GetCalculator();
Type calcType = calc.GetType();
object res = calcType.InvokeMember(
"Add", BindingFlags.InvokeMethod,
null, new object[] { 10, 20 });
int sum = Convert.ToInt32(res);La fonction renvoie un calculateur, mais le système ne reconnaît pas exactement le type d'objet de ce calculateur au moment de la compilation. La seule chose sur laquelle le code s'appuie est que cet objet doit avoir la méthode Add (ajouter). Notez que vous n'obtenez pas IntelliSense pour cette méthode parce que vous donnez son nom comme un littéral de chaîne.
Avec le mot clé dynamique, ce code est aussi simple que celui-ci :
dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);Les hypothèses sont les mêmes : il y a un objet avec un type inconnu qui devrait avoir la méthode Add (ajouter). Et comme dans l'exemple précédent, vous n'obtenez pas IntelliSense pour cette méthode. Mais la syntaxe est plus facile à lire et à utiliser, et ressemble à un code qui appelle une méthode .NET typique.
Un autre exemple où dynamique peut être utile est de créer un ensemble de méthodes dynamiques qui sont des objets pouvant ajouter ou supprimer des propriétés et des méthodes au moment de l'exécution.
Le .NET Framework 4 a un nouvel espace de noms : System.Dynamic. Cet espace de noms est en fait intégré à le DLR. Les classes System.Dynamic.ExpandoObject et System.Expando.DynamicObject avec le nouveau mot clé dynamique peuvent vous permettre de créer des structures et des hiérarchies dynamiques d'une manière claire et facile à lire.
Par exemple, voici comment vous pouvez ajouter une propriété et une méthode en utilisant la classe ExpandoObject :
dynamic expando = new ExpandoObject();
expando.SampleProperty =
"This property was added at run time";
expando.SampleMethod = (Action)(
() => Console.WriteLine(expando.SampleProperty));
expando.SampleMethod();Pour des scénarios plus détaillés sur les classes ExpandoObject et DynamicObject, consultez la documentation MSDN. Les articles « Dynamic Method Bags » de Bill Wagner (msdn.microsoft.com/library/ee658247) et « Dynamic in C# 4.0 : Introducing the ExpandoObject » sur le blog Foire aux questions C# (bit.ly/amRYRw) sont également très utiles.
Vous pouvez donner une meilleure syntaxe à votre propre bibliothèque ou créer un wrapper pour une bibliothèque existante. Ce scénario est d'un niveau plus élevé comparé aux deux précédents et requiert une meilleure connaissance des spécificités du DLR.
Pour des cas simples, vous pouvez utiliser la classe DynamicObject. Dans cette classe, vous pouvez combiner la déclaration statique des méthodes et des propriétés avec la distribution dynamique. Vous pouvez ainsi stocker un objet auquel vous voulez donner une meilleure syntaxe dans une propriété de classe, tout en traitant toutes les opérations avec cet objet par une distribution dynamique.
Prenez la classe DynamicString de la figure 1 qui encapsule une chaîne et affiche les noms de toutes les méthodes avant de faire appel à ces méthodes par réflexion.
Figure 1 DynamicString
public class DynamicString : DynamicObject {
string str;
public DynamicString(string str) {
this.str = str;
}
public override bool TryInvokeMember(
InvokeMemberBinder binder, object[] args,
out object result) {
Console.WriteLine("Calling method: {0}", binder.Name);
try {
result = typeof(string).InvokeMember(
binder.Name,
BindingFlags.InvokeMethod |
BindingFlags.Public |
BindingFlags.Instance,
null, str, args);
return true;
}
catch {
result = null;
return false;
}
}
}Pour instancier cette classe, il est conseillé d'utiliser le mot clé dynamique :
dynamic dStr = new DynamicString("Test");
Console.WriteLine(dStr.ToUpper());
Console.ReadLine();Bien entendu, ce exemple particulier est fictif et n'est pas vraiment efficace. Mais si vous avez un API qui s'appuie fortement sur la réflexion, vous pouvez encapsuler tous les appels par réflexion tel qu'indiqué ici afin que les utilisateurs finaux de votre API ne les voient pas.
Pour plus d'exemples, consultez la documentation MSDN (msdn.microsoft.com/library/system.dynamic.dynamicobject) et le billet « Dynamic in C# 4.0 : Creating Wrappers with DynamicObject » sur le blog Foire aux questions C# (bit.ly/dgS3od).
Comme je l'ai mentionné, la classe DynamicObject est fournie par le DLR. DynamicObject ou ExpandoObject est tout ce qu'il vous faut pour produire un objet dynamique. Cependant, certains objets dynamiques ont une logique de liaison compliquée pour accéder aux membres ou pour invoquer des méthodes. De tels objets doivent implémenter l'interface IDynamicMetaObjectProvider et fournir leur propre distribution dynamique. Ce scénario est de niveau avancé et ceux qui sont intéressés peuvent lire « Implementing Dynamic Interfaces » de Bill Wagner (msdn.microsoft.com/vcsharp/ff800651) et « Getting Started with the DLR as a Library Author » de Alex Turner et Bill Chiles (dlr.codeplex.com).
Les scripts constituent un moyen puissant qui donne de l'extensibilité à votre application. Microsoft Office en est un bon exemple dans le cas suivant : de nombreuse macros, des modules complémentaires et des plug-ins existent avec Visual Basic pour Applications (VBA). Désormais, le DLR vous permet de créer des applications scriptables parce qu'il fournit un ensemble commun d'API d'hébergement pour les langages.
Par exemple, vous pouvez créer une application où les utilisateurs peuvent ajouter une fonctionnalité par eux-mêmes sans demander de nouvelles fonctionnalités pour le produit principal, comme ajouter de nouveaux caractères et des cartes à un jeu, ou de nouveaux graphismes à une application métier.
Vous devez utiliser la version open source du DLR à partir de dlr.codeplex.com plutôt que celle utilisée par le .NET Framework 4 parce que pour l'instant, l'écriture de script et l'hébergement des API sont uniquement disponibles dans la version open source. En outre, on part du principe que vous écrirez des scripts, non avec C#, mais plutôt dans un des langages dynamiques du .NET, tels que IronPython ou IronRuby. Cependant, tout langage peut prendre en charge ces API, même ceux qui ne sont pas implémentés avec le DLR.
Pour plus de détails sur la façon d'utiliser cette fonctionnalité, regardez la présentation « Using Dynamic Languages to Build Scriptable Applications » e Dino Viehland à PDC09 (microsoftpdc.com/2009/FT30).
Comment distinguer les objets dynamiques des autres objets ? Un moyen facile consiste à utiliser des fonctionnalités intégrées de l'IDE. Vous pouvez placer le pointeur de la souris sur l'objet pour voir son type de déclaration ou vérifier si IntelliSense est disponible (voir Figure 2).
Figure 2 Objet dynamique dans Visual Studio
Au moment de l'exécution cependant, les choses deviennent plus compliquées. Vous ne pouvez pas vérifier si la variable a été déclarée par le mot clé dynamique ; le type d'exécution de l'objet dynamique est le type de valeur qu'il stocke et vous ne pouvez pas obtenir sa déclaration de type statique. De même quand vous déclarez votre variable comme objet : au moment de l'exécution, vous ne pouvez avoir qu'un type de valeur de la variable et vous ne pouvez pas savoir si cette variable était à l'origine déclarée comme objet.
Cependant, vous pouvez déterminer au moment de l'exécution si un objet provient du DLR. Cela peut être important dans la mesure où les objets de type ExpandoObject et DynamicObject peuvent changer de comportement au moment de l'exécution, notamment les propriétés et les méthodes d'ajout et de suppression.
De plus, vous ne pouvez pas utiliser des méthodes de réflexion standard pour obtenir des informations sur de tels objets. Si vous ajoutez une propriété à une instance de la classe ExpandoObject, vous ne pouvez obtenir cette propriété à partir de la réflexion :
dynamic expando = new ExpandoObject();
expando.SampleProperty = "This property was added at run time";
PropertyInfo dynamicProperty =
expando.GetType().GetProperty("SampleProperty");
// dynamicProperty is null.L'avantage, c'est que dans le .NET Framework 4, tous les objets qui peuvent ajouter ou supprimer des membres de manière dynamique doivent implémenter une interface spécifique : System.Dynamic.IDynamicMetaObjectProvider. Les classes DynamicObject et ExpandoObject implémentent également cette interface. Néanmoins, cela ne veut pas dire que tout objet déclaré en utilisant le mot clé dynamique implémente cette interface.
dynamic expando = new ExpandoObject();
Console.WriteLine(expando is IDynamicMetaObjectProvider);
// True
dynamic test = "test";
Console.WriteLine(test is IDynamicMetaObjectProvider);
// FalseDonc, si vous utilisez dynamique avec la réflexion, rappelez-vous que la réflexion ne fonctionnera pas avec des propriétés et des méthodes ajoutées de façon dynamique et qu'il faut vérifier que l'objet auquel vous vous intéressez implémente l'interface IDynamicMetaObjectProvider.
Le scénario COM interop que l'équipe C# a précisément ciblé pour la publication de C# 4 a été la programmation sur les applications Microsoft Office, telles que Word et Excel. Le but était de rendre cette tâche facile et normale en C# comme c'est déjà le cas en Visual Basic. C'est aussi un aspect de la stratégie de coévolution de Visual Basic et de C#, où les deux langages tendent vers la parité des fonctionnalités et l'emprunt mutuel des solutions les plus productives et les meilleures.
Si vous souhaitez obtenir plus de détails, lisez l'article « C# and VB coevolution » sur le blog Visual Studio de Scott Wiltamuth (bit.ly/bFUpxG).
La figure 3 montre le code C# 4 qui ajoute une valeur à la première cellule dans un tableur Excel, puis applique la méthode AutoFit à la première colonne. Les commentaires au-dessous de chaque ligne montre les équivalents précédents en C# 3.0 et des versions antérieures.
Figure 3 Écriture de scripts Excel avec C#
// Add this line to the beginning of the file:
// using Excel = Microsoft.Office.Interop.Excel;
var excelApp = new Excel.Application();
excelApp.Workbooks.Add();
// excelApp.Workbooks.Add(Type.Missing);
excelApp.Visible = true;
Excel.Range targetRange = excelApp.Range["A1"];
// Excel.Range targetRange = excelApp.get_Range("A1", Type.Missing);
targetRange.Value = "Name";
// targetRange.set_Value(Type.Missing, "Name");
targetRange.Columns[1].AutoFit();
// ((Excel.Range)targetRange.Columns[1, Type.Missing]).AutoFit();Ce qui est intéressant dans cet exemple, c'est que vous ne verrez nulle part le mot clé dynamique dans le code. En fait, il est utilisé sur une seule ligne ici :
targetRange.Columns[1].AutoFit();
// ((Excel.Range)targetRange.Columns[1, Type.Missing]).AutoFit();Dans la version C# 3.0, targetRange.Columns[1, Type.Missing] renvoie l'objet, c'est pourquoi la conversion en Excel.Range est nécessaire. Mais dans C# 4 et Visual Studio 2010, de tels appels sont discrètement convertis en appels dynamiques. Ainsi, le type de targetRange.Columns[1] en C# 4 est en fait dynamique.
Un autre aspect d'importance : les améliorations du COM interop en C# 4 ne concernent pas que le mot clé Dynamic. Sur toutes les autres lignes, une meilleure syntaxe est obtenue grâce à de nouvelles fonctionnalités, telles que les propriétés indexées et les paramètres optionnels. Vous trouverez une bonne présentation de ces nouvelles fonctionnalités dans l'article MSDN Magazine intitulé « New C# Features in the .NET Framework 4 » de Chris Burrows (msdn.microsoft.com/magazine/ff796223).
J'espère que cet article a abordé la plupart des questions que vous pouvez avoir sur le mot clé Dynamic dans C# 4, mais je suis sûr qu'il en reste encore Si vous avez des commentaires, des questions ou des suggestions, envoyez-les à dlr.codeplex.com/discussions et posez vos questions. Quelqu'un a peut-être déjà posé des questions sur le sujet, ou vous pouvez lancer une nouvelle discussion. Nous avons une communauté active et nous accueillons de nouveaux membres.
Alexandra Rusina est responsable de programme dans l'équipe Silverlight. Avant cela, elle était rédactrice en programmation dans l'équipe Langages Visual Studio sur la version Visual Studio 2010. Elle animait aussi régulièrement un blog portant sur la Foire aux questions C# (blogs.msdn.com/b/csharpfaq/).
Je remercie notre expert technique d'avoir relu cet article : Bill Chiles