À la pointe

Utilisation du mot clé dynamic en C# 4.0

Dino Esposito

Dino EspositoL'introduction de la vérification de type statique a constitué une étape importante dans l'histoire des langages de programmation. Au cours des années 70, les langages tels que le Pascal et le C ont commencé à mettre en application les types statiques et la vérification du type fort. Avec la vérification de type statique, le compilateur produit une erreur chaque fois qu'un appel échoue à passer un argument de méthode du type approprié. De même, vous devez vous attendre à une erreur de compilateur si vous essayez d'appeler une méthode manquante sur une instance de type.

D'autres langages qui mettent en avant l'approche contraire, la vérification de type dynamique, font maintenant partie du paysage. La vérification de type dynamique contredit l'idée que le type d'une variable doit être statiquement déterminé au moment de la compilation et ne plus changer tant que la variable se trouve dans l'étendue. Notez, toutefois, que la vérification de type dynamique ne permet pas de mélanger les types, en prétendant qu'ils sont identiques. Par exemple, même avec la vérification de type dynamique, vous ne pouvez toujours pas ajouter une valeur booléenne à un entier. La différence avec la vérification de type statique réside dans le fait que la vérification se produit lors de l'exécution du programme plutôt que lors de sa compilation.

Statiquement typé ou dynamiquement typé

Visual Studio 2010 et C# 4.0 offrent un nouveau mot clé, dynamic, qui permet la saisie dynamique dans ce qui a traditionnellement toujours été un langage statiquement typé. Avant de plonger dans les aspects dynamiques de C# 4.0, nous devons tout de même passer en revue la terminologie en vigueur dans ce domaine.

Définissons une variable comme un emplacement de stockage qui est restreint aux valeurs d'un type particulier. Spécifions ensuite les quatre propriétés fondamentales d'un langage statiquement typé :

  • Chaque expression est d'un type connu à la compilation.
  • Les variables sont limitées à un type connu à la compilation.
  • Le compilateur garantit que les restrictions de type affectant les assignations d'expressions en variables respectent les restrictions sur les variables.
  • Les tâches d'analyse sémantique, telles que la résolution de surcharge, se produisent au moment de la compilation et les résultats sont gravés dans l'assembly.

Un langage dynamique possède les propriétés contraires. Toutes les expressions ne sont pas d'un type connu au moment de la compilation, pas plus que toutes les variables. Les restrictions de stockage, si elles existent, sont vérifiées au moment de l'exécution et ignorées au moment de la compilation. L'analyse sémantique se ne produit qu'au moment de l'exécution.

Un langage statiquement typé vous permet de rendre certaines opérations dynamiques. L'opérateur de conversion existe pour vous permettre de tenter une conversion de type lors de l'exécution. La conversion fait partie du code du programme, et vous pouvez résumer la sémantique exprimée par l'opérateur de conversion sous la description « vérifier dynamiquement la validité de cette conversion lors de l'exécution ».

Toutefois, en ce qui concerne les attributs tels que dynamique et statique (voire fort et faible) : Aujourd'hui, ils s'appliquent mieux à des fonctions individuelles d'un langage de programmation qu'au langage tout entier.

Intéressons-nous rapidement à Python et PHP. Tous deux sont des langages dynamiques, vous permettent d'utiliser des variables et autorisent l'environnement d'exécution à déterminer le type stocké à l'intérieur. Mais avec PHP vous pouvez stocker, disons, des entiers et des chaînes dans la même variable et dans la même étendue. À cet égard, PHP (comme JavaScript) est un langage dynamique faiblement typé.

D'un autre côté, Python ne vous donne qu'une seule chance de définir le type d'une variable, ce qui le rend plus fortement typé. Vous pouvez assigner dynamiquement le type à une variable et laisser le runtime le déduire d'après la valeur assignée. Mais après cela, vous n'êtes pas autorisé à stocker la moindre valeur d'un type inapproprié dans cette variable.

Types dynamiques en C#

C# 4.0 possède des fonctions qui le rendent à la fois dynamique et statique, ainsi qu'à la fois faiblement et fortement typé. Bien que né en tant que langage statiquement typé, C# devient dynamiquement typé dans tout contexte où vous utilisez le mot clé dynamic, comme suit :

dynamic number = 10;
Console.WriteLine(number);

Et étant donné que dynamic est un mot clé contextuel, pas un mot clé réservé, il conserve sa propriété même en présence de variables ou de méthodes existantes nommées dynamic.

Notez que C# 4.0 ne vous force pas à utiliser dynamic, pas plus que C# 3.0 ne vous obligeait à utiliser var, les lambdas ou les initialiseurs d'objet. C# 4.0 propose spécifiquement le nouveau mot clé dynamic pour rendre certains scénarios bien connus plus faciles à traiter. Le langage demeure essentiellement statiquement typé, même s'il offre la capacité d'interagir de manière plus efficace avec les objets dynamiques.

Pour quelle raison pourriez-vous souhaiter utiliser un objet dynamique ? Premièrement, vous pouvez ne pas connaître le type de l'objet auquel vous avez affaire. Vous pouvez avoir des indices, mais pas la certitude de typer statiquement une variable donnée—ce qui est le cas dans de nombreuses situations, comme lorsque vous travaillez avec des objets COM, ou vous utilisez la réflexion pour saisir des instances. Dans ce contexte, le mot clé dynamic rend certaines situations moins difficiles à gérer. Le code écrit avec dynamic est plus facile à lire et à écrire, et contribue à produire des applications plus simples à comprendre et à entretenir.

Deuxièmement, votre objet peut être de nature changeante de manière inhérente. Vous pouvez travailler avec des objets créés dans des environnements de programmation dynamiques tels que IronPython et IronRuby. Mais vous pouvez aussi utiliser cette fonctionnalité avec des objets HTML DOM (sujets aux propriétés expando) et des objets Microsoft .NET Framework 4 spécifiquement créés pour avoir des natures dynamiques.

Utilisation de dynamic

Il est important de comprendre que dans le système de type de C#, dynamic est un type. Il a une signification très particulière, mais il s'agit absolument d'un type et il est important de le traiter comme tel. Vous pouvez indiquer dynamic comme le type d'une variable que vous déclarez, le type d'éléments dans une collection ou la valeur de retour d'une méthode. Vous pouvez aussi utiliser dynamic comme le type d'un paramètre de méthode. À l'inverse, vous ne pouvez pas utiliser dynamic avec le type d'opérateur et ne pouvez pas l'utiliser comme type de base d'une classe.

Le code suivant montre comment déclarer une variable dynamique dans le corps d'une méthode :

public void Execute()  { 
  dynamic calc = GetCalculator();
  int result = calc.Sum(1, 1);
}

Si vous en savez suffisamment sur le type de l'objet renvoyé par la méthode GetCalculator, vous pouvez déclarer la variable calc de ce type, ou déclarer la variable en tant que var, laissant le compilateur déterminer les détails exacts. Mais utiliser var ou un type statique explicite nécessiterait que vous soyez certain de l'existence d'une méthode Sum sur le contrat exposé par le type renvoyé par GetCalculator. Si la méthode n'existe pas, vous obtenez une erreur de compilation.

Avec dynamic, vous retardez toute décision à propos de l'exactitude de l'expression au moment de l'exécution. Le code se compile et est résolu à l'heure de l'exécution tant qu'une méthode Sum est disponible sur le type stocké dans la variable calc.

Vous pouvez également utiliser le mot clé pour définir une propriété sur une classe. Ce faisant, vous pouvez décorer le membre avec n'importe quel modificateur de visibilité de votre choix, tel que publique, protégé et même statique.

La figure 1 illustre la versatilité du mot clé dynamic. Dans le programme principal, j'ai une variable dynamique instanciée avec la valeur de retour d'un appel de fonction. Cela ne serait pas grave si la fonction ne recevait ni ne retournait pas d'objet dynamique. Il est intéressant de voir ce qui se produit lorsque, comme dans l'exemple, vous passez un nombre, puis essayez de le doubler dans la fonction.

Figure 1 Utilisation de dynamic dans la signature d'une fonction

class Program {
  static void Main(string[] args) {
    // The dynamic variable gets the return 
    // value of a function call and outputs it.
    dynamic x = DoubleIt(2);
    Console.WriteLine(x);

    // Stop and wait
    Console.WriteLine(“Press any key”);
    Console.ReadLine();
  }

  // The function receives and returns a dynamic object 
  private static dynamic DoubleIt(dynamic p) {
    // Attempt to "double" the argument whatever 
    // that happens to produce
    
    return p + p;
  }
}

Si vous entrez une valeur de 2 et essayez ce code, vous recevez une valeur de 4. Si vous entrez 2 en tant que chaîne, vous obtiendrez alors 22. Dans la fonction, l'opérateur + est résolu dynamiquement sur la base du type d'exécution des opérandes. Si vous changez le type en System.Object, vous obtenez une erreur de compilation, car l'opérateur + n'est pas défini sur System.Object. Le mot clé dynamic rend possible des scénarios qui ne le seraient pas sans lui.

dynamic ou System.Object

Jusqu'à .NET Framework 4, qu'une méthode retourne différents types en fonction de conditions différentes n'était possible qu'en ayant recours à une classe de base commune. Vous avez probablement résolu ce problème en recourant à System.Object. Une fonction qui retourne System.Object met à la disposition de l'appelant une instance pouvant être convertie en presque n'importe quoi. Donc, en quoi l'utilisation de dynamic est-elle préférable à l'utilisation de System.Object ?

En C# 4, le type effectif derrière la variable qui est déclarée dynamique est résolu au moment de l'exécution, et le compilateur part simplement du principe que l'objet dans une variable déclarée dynamique prend en charge n'importe quelle opération. Cela signifie que vous pouvez vraiment écrire du code qui appelle une méthode sur l'objet que vous vous attendez à trouver au moment de l'exécution, comme illustré ici :

dynamic p = GetSomeReturnValue();
p.DoSomething();

En C# 4.0, le compilateur n'aura rien à redire à propos de ce code. Un code analogue utilisant System.Object ne se compile pas et nécessite quelques manipulations de votre part—réflexion ou conversion aventureuse—pour fonctionner.

var ou dynamic

Les mots clés var et dynamic ne sont similaires qu'en apparence. Var indique que le type de la variable doit être défini au type à l'heure de la compilation de l'initialiseur.

Mais dynamic signifie que le type de la variable est du type dynamique qui existe en C# 4.0. Au bout du compte, dynamic et var ont des significations assez opposées. Var vise à renforcer et à améliorer le typage statique. Il veille à ce que le type d'une variable soit déduit par le compilateur en observant le type exact renvoyé par l'initialiseur.

Le mot clé dynamic vise essentiellement à éviter le typage statique. Utilisé dans une déclaration de variable, dynamic instruit le compilateur de cesser d'essayer de déterminer le type de la variable. Le type doit tendre à être celui qu'il aura au moment de l'exécution. Avec var, votre code est aussi statiquement typé qu'il l'aurait été si vous aviez opté pour l'approche classique consistant à utiliser des types explicites dans une déclaration de variable.

Une autre différence entre les deux mots clés est que var ne peut apparaître que dans une déclaration de variable locale. Vous ne pouvez pas utiliser var pour définir une propriété sur une classe, pas plus que vous ne pouvez l'utiliser pour spécifier la valeur de retour ou le paramètre d'une fonction.

En tant que développeur, vous utilisez le mot clé dynamic avec des variables censées contenir des objets de type incertain tels que des objets renvoyés d'une API COM ou DOM ; obtenus d'un langage dynamique (IronRuby, par exemple) ; provenant de réflexion ; provenant d'objets construits dynamiquement en C# 4.0 à l'aide des nouvelles fonctions d'extension.

En revanche, le type dynamique ne passe pas outre les vérifications de type. Il se contente de les faire tous passer en mode d'exécution. Si des incompatibilités de type sont découvertes au moment de l'exécution, des exceptions sont déclenchées.

Dino Esposito est l'auteur de « Programming ASP.NET MVC » chez Microsoft Press et co-auteur de « Microsoft .NET: Architecting Applications for the Enterprise » (Microsoft Press, 2008). Basé en Italie, Dino Esposito participe régulièrement aux différentes manifestations du secteur organisées aux quatre coins du monde. Vous trouverez son blog à l'adresse : weblogs.asp.net/despos.

Je remercie  Eric Lippert, expert technique, d'avoir relu cet article.