Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

C# 4.0 : les nouveautés du langage

Visual Studio 2010 beta 2 est maintenant accessible au public et il devient donc possible de vous parler des nouveautés sans risque de violer le NDA qui courrait jusqu’à lors pour les MVP et autres early testers de ce produit.

Les évolutions du langage commencent à se tasser et la mouture 4.0 est assez loin des annonces fracassantes qu’on a pu connaître avec l’arrivée des génériques ou des classes statiques et autres nullables de C# 2.0, ni même avec LINQ ou les expressions Lambda de C# 3.0.

Pour la version 4 du langage on compte pour l’instant peu d’ajouts (le produit ne sortira qu’en 2010 et que d’autres features pourraient éventuellement apparaître). On peut regrouper les 3 principales nouveautés ainsi :

  • Les type dynamiques (dynamic lookup)
  • Les paramètres nommés et les paramètres optionnels
  • Covariance et contravariance

Paramètres optionnels

Il est en réalité bien étrange qu’il ait fallu attendre 4 versions majeures de C# pour voir cette syntaxe de Delphi refaire surface tellement son utilité est évidente.
De quoi s’agit-il ?  Vous avez tous écrits du code C# du genre :

   1:  MaMethode(typeA param1, typeB param2, typeC param3) …; 
   2:  MaMethode(typeA param1, typeB param2) { MaMethode(param1, param2, null) } 
   3:  MaMethode(typeA param1) { MaMethode(param1, null) } 
   4:  MaMethode() { MaMethode(null) }

Et encore cela n’est qu’un exemple bien court. Des librairies entières ont été écrites en C# sur ce modèle afin de permettre l’appel à une même méthode avec un nombre de paramètres variable. Le Framework lui-même est écrit comme cela.
Bien sûr il existe “params” qui autorise dans une certaine mesure une écriture plus concise, mais dans une certaine mesure seulement. Dans l’exemple ci-dessus le remplacement des valeurs manquantes par des nulls est une simplification. Dans la réalité les paramètres ne sont pas tous des objets ou des nullables. Dans ces cas là il faut spécifier des valeurs bien précises aux différents paramètres omis. Chaque valeur par défaut se nichant dans le corps de chacune des versions de la méthode, pour retrouver l’ensemble de ceux-ci il faut donc lire toutes les variantes et reconstituer de tête la liste. Pas très pratique.

Avec C# 4.0 cette pratique verbeuse et inefficace prend fin. Ouf !
Il est donc possible d’écrire une seule version de la méthode comme cela :

   1:  MaMethode(bool param1=false, int param2=25, MonEnum param3 = MonEnum.ValeurA)  …

Grâce à cette concision l’appel à “MaMethode(true)” sera équivalente à “MaMethode(true, 25, MonEnum.ValeurA)”. Le premier paramètre est fixé par l’appelant (c’est un exemple), mais les deux autres étant oubliés ils se voient attribuer automatiquement leur valeur par défaut.

Pas de surcharges inutiles de la méthode, toutes les valeurs par défaut sont accessibles dans une seule déclaration. Il reste encore quelques bonnes idées dans Delphi que Anders pourraient reprendre comme les indexeurs nommés ou les if sans nécessité de parenthèses systématiques. On a le droit de rêver :-)

Comme pour se faire pardonner d’avoir attendu 4 versions pour ressortir les paramètres par défaut de leur carton, C# 4.0 nous offre un petit supplément :

Les paramètres nommés

Les paramètres optionnels c’est sympa et pratique, mais il est vrai que même sous Delphi il restait impossible d’écrire du code tel quel “MaMethode(true,,MonEnum.ValeurA)”. En effet, tout paramètre doit recevoir une valeur et les paramètres “sautés” ne peuvent être remplacés par des virgules ce qui rendrait le code totalement illisible. C# 4.0 n’autorise pas plus ce genre de syntaxe, mais il offre la possibilité de ne préciser que quelques uns des paramètres optionnels en donnant leur nom.

La technique est proche de celle utilisée dans les initialiseurs de classe qui permettent d’appeler un constructeur éventuellement sans paramètre et d’initialiser certaines propriétés de l’instance en les nommant. Ici c’est entre les parenthèses de la méthode que cela se jouera. Pour suivre notre exemple précédent, si on veut ne fixer que la valeur de “param3” il suffit d’écrire :

   1:  MaMethode(param3 : MonEnum.ValeurZ); 

de même ces syntaxes seront aussi valides :

   1:  MaMethode(true,param3:MonEnum.ValeurX); 
   2:  MaMethode(param3:MonEnum.ValeurY,param1:false); 

En effet, l’ordre n’est plus figé puisque les noms lèvent toute ambigüité. Quant aux paramètres omis, ils seront remplacés par leur valeur par défaut.

Voici donc une amélioration syntaxique qui devrait simplifier beaucoup le code de nombreuses librairies, à commencer par le Framework lui-même !

Dynamique rime avec Polémique

Autre nouveauté de C# 4.0, les types dynamiques. Aie aie aie…

Dynamique. C’est un mot qui fait jeune, sautillant, léger. Hélas. Car cela ne laisse pas présager du danger que représente cette extension syntaxique ! La polémique commence ici et, vous l’aurez compris, je ne suis pas un fan de cette nouveauté :-)

Techniquement et en deux mots cela permet d’écrire “MaVariable.MethodeMachin()” sans être sûr que l’instance pointée par MaVariable supporte la méthode MethodeMachin(). Et ça passe la compilation sans broncher. Si çà pète à l’exécution, il ne faudra pas venir se plaindre. Le danger du nouveau type “dynamic” est bien là. Raison de mes réticences…

Si on essaye d’être plus positif il y a bien sûr des motivations réelles à l’implémentation des dynamiques. Par exemple le support par .NET des langages totalement dynamiques comme Python et Ruby (les dynamique de C# 4 s’appuient d’ailleurs sur le DLR), même si ces langages sont plus des gadgets amusants que l’avenir du développement (avis personnel). Les dynamiques simplifient aussi l’accès aux objets COM depuis IDispatch, mais COM n’est pas forcément non plus l’avenir de .NET (autre avis personnel).

Les deux autres emplois des dynamiques qui peuvent justifier leur existence sont l’accès simplifié à des types .NET au travers de la réflexion (pratique mais pas indispensable) ou bien des objets possédant une structure non figée comme les DOM HTML (pratique mais à la base de pas mal de code spaghetti).

Bref, les dynamiques ça peut être utile dans la pratique, mais ce n’est pas vraiment une nouvelle feature améliorant C# (comme les autres ajouts jusqu’à maintenant). Le danger de supporter un tel type est-il compensé par les quelques avantages qu’il procure ? C’est là que dynamique rime avec polémique !
Pour moi la réponse est non, mais je suis certain que ceux qui doivent jongler avec du COM ou des DOM Html penseront le contraire.

J’arrête de faire le grognon pour vous montrer un peu mieux la syntaxe. Car malgré tout le dynamisme n’est pas une invitation au chaos. Enfin si. Mais un chaos localisé. C’est à dire que l’appel à une méthode non existante reste impossible partout, sauf pour un objet déclaré avec le nouveau type “dynamic” :

   1:  dynamic x; 
   2:  x = Machin.ObtientObjetDynamique(); 
   3:  x.MethodeA(85); // compile dans tous les cas
   4:   
   5:  dynamic z = 6; // conversion implicite 
   6:  int i = z; // sorte de unboxing automatique
   7:   

Bien entendu le “dynamisme” est total : cela fonctionne sur les appels de méthodes autant que sur les propriétés, les délégués, les indexeurs, etc.

Le compilateur va avoir pour charge de collecter le maximum d’information sur l’objet dynamique utilisé (comment il est utilisé, ses méthodes appelées…), charge au runtime du Framework de faire le lien avec la classe de l’instance qui se présentera à l’exécution. C’est du late binding avec tout ce qui va avec notamment l’impossibilité de contrôler le code à la compilation.

A vous de voir, mais personnellement je déconseille fortement l’utilisation des dynamiques qui sont comme un gros interrupteur ajouté en façade de C# “Langage Fortement Typé On/Off”. Restez dans le mode “On” et ne passez jamais en mode “Off” !

Covariance et Contravariance ou le retour de l’Octothorpe

J’adore le jargon de notre métier. “Comment passer pour un hasbeen en deux secondes à la machine à café” est une mise en situation comique que j’utilise souvent, certainement influencé par mon passé dans différentes grosses SSII parisiennes et par la série Caméra Café de M6…
Ici vous aurez l’air stupide lorsque quelqu’un lancera “Alors t’en penses quoi de la contravariance de C#4.0 ?”… L’ingé le plus brillant qui n’a pas lu les blogs intéressants la veille sera dans l’obligation de plonger le nez dans son café et de battre en retraire piteusement, prétextant un truc urgent à finir…

Covariance et contravariance sont des termes académiques intimidants. Un peu comme si on appelait C# “C Octothorpe”. On aurait le droit. Octothorpe est l’un des noms du symbole #. Mais franchement cela serait moins sympathique que “do dièse” (C# est la notation de do dièse en américain, à condition de prononcer le # comme “sharp” et non “square” ou “octothorpe”).

Un support presque parfait sous C# 1 à 3

Un peu comme monsieur Jourdain faisait de la prose sans le savoir, la plupart d’entre nous a utilisé au moins la covariance en C# car il s’agit de quelque chose d’assez naturel en programmation objet et que C# le supporte pour la majorité des types. D’ailleurs la covariance existe depuis le Framework 2.0 mais pour certains cas (couverts par C# 4.0) il aurait fallu émettre directement du code IL pour s’en servir.

C# 4.0 n’ajoute donc aucune nouvelle fonctionnalité ou concept à ce niveau, en revanche il comble une lacune des versions 1 à 3 qui ne supportaient pas la covariance et la contravariance pour les délégués et les interfaces dans le cadre de leur utilisation avec les génériques. Un cas bien particulier mais devant lequel on finissait pas tomber à un moment ou un autre.

Un besoin simple

C# 4.0 nous assure simplement que les choses vont fonctionner comme on pourrait s’y attendre, ce qui n’était donc pas toujours le cas jusqu’à lors.

Les occasions sont rares où interfaces et délégués ne se comportent pas comme prévu sous C#, très rares. Mais cela peut arriver. Avec C# 4.0 ce sont ces situations rares qui sont supprimées. De fait on pourrait se dire qu’il n’y a rien à dire sur cette nouveauté de C# 4.0 puisqu’on utilisait la covariance et la contravariance sans s’en soucier et que la bonne nouvelle c’est qu’on va pouvoir continuer à faire la même chose !

Mais s’arrêter là dans les explications serait un peu frustrant.

Un exemple pour mieux comprendre

Supposons  les deux classes suivantes :

   1:  class Animal{ } 
   2:  class Chien: Animal{ } 

La seconde classe dérive de la première. Imaginons que nous écrivions maintenant un délégué définissant une méthode retournant une instance d’un type arbitraire :

   1:  delegate T MaFonction<T>();

Pour retourner une instance de la classe Chien nous pouvons écrire :

   1:  MaFonction<Chien> unChien = () => new Chien();

Vous noterez l’utilisation d’une expression Lambda pour définir le délégué. Il s’agit juste d’utiliser la syntaxe la plus concise. On pourrait tout aussi bien définir d’abord une fonction retournant un Chien, lui donner un nom, puis affecter ce dernier à la variable “unChien” comme dans le code ci-dessous :

   1:  public Chien GetChien()
   2:  {
   3:      return new Chien();
   4:  }
   5:   
   6:  MaFonction<Chien> unChien = GetChien; // sans les () bien sur !
   7:   

Partant de là, il est parfaitement naturel de se dire que le code suivant est valide :

   1:  MaFonction<Animal> animal = unChien;

En effet, la classe Chien dérivant de Animal, il semble légitime de vouloir utiliser le délégué de cette façon. Hélas, jusqu’à C# 3.0 le code ci-dessus ne compile pas.

La Covariance

La covariance n’est en fait que la possibilité de faire ce que montre le dernier exemple de code. C# 4.0 introduit les moyens d’y arriver en introduisant une nouvelle syntaxe. Cette dernière consiste tout simplement à utiliser le mot clé “out” dans la déclaration du délégué:

   1:  delegate T MaFonction<out T>();

Le mot clé “out” est déjà utilisé en C# pour marquer les paramètres de sortie dans les méthodes. Mais il s’agit bien ici d’une utilisation radicalement différente. Pourquoi “out” ? Pour marquer le fait que le paramètre sera utilisé en “sortie” de la méthode.

La covariance des délégués sous C# 4.0 permet ainsi de passer un sous-type du type attendu à tout délégué qui produit en sortie (out) le type en question.

Si vous pensez que tout cela est bien compliqué, alors attendez deux secondes que je vous parle de contravariance !

La Contravariance

Si la covariance concerne les délégués et les interfaces utilisés avec les types génériques dans le sens de la sortie (out), et s’il s’agit de pouvoir utiliser un sous-type du type déclaré, ce qui est très logique en POO, la contravariance règle un problème inverse : autoriser le passage d’un super-type non pas en sortie mais en entrée d’une méthode.

Un exemple de contravariance

Pas de panique ! un petit exemple va tenter de clarifier cette nuance :

   1:  delegate void Action1<in T>(T a);
   2:   
   3:  Action1<Animal> monAction = (animal) => { Console.WriteLine(animal); };
   4:  Action1<Chien> chien1 = monAction;

Bon, ok. Paniquez. !!!

Ici un délégué est défini comme une méthode ayant un paramètre de type arbitraire. Le mot clé “in” remplace “out” de la covariance car le paramètre concerné est fourni en entrée de la méthode (in).

La plupart des gens trouve que la contravariance est moins intuitive que la covariance, et une majorité de développeurs trouve tout cela bien complexe. Si c’est votre cas vous êtes juste dans la norme, donc pas de complexe :-)

La contravariance se définit avec le mot clé “in” simplement parce que le type concerné est utilisé comme paramètre d’entrée. Encore une fois cela n’a rien à voir avec le sens de “in” dans les paramètres d’entrée des méthodes. Tout comme “out” le mot clé “in” est utilisé ici dans un contexte particulier, au niveau de la déclaration d’un type générique dans un délégué.

Avec la contravariance il est donc possible de passer un super-type du type déclaré. Cela semble contraire aux habitudes de la POO (passer un sous-type d’un type attendu est naturel mais pas l’inverse). En réalité la contradiction n’est que superficielle. Dans le code ci-dessus on s’aperçoit qu’en réalité “monAction” fonctionne avec n’importe quelle instance de “Animal”, un Chien étant un Animal, l’assignation est parfaitement légitime !

M’sieur j’ai pas tout compris !

Tout cela n’est pas forcément limpide du premier coup, il faut l’avouer.

En réalité la nouvelle syntaxe a peu de chance de se retrouver dans du code “de tous les jours”. En revanche cela permet à C# de supporter des concepts de programmation fonctionnelle propres à F# qui, comme par hasard, est aussi fourni de base avec .NET 4.0 et Visual Studio 2010. Covariance et contravariance seront utilisées dans certaines librairies et certainement dans le Framework lui-même pour que, justement, les délégués et les interfaces ainsi définis puissent être utilisés comme on s’y attend. La plupart des développeurs ne s’en rendront donc jamais compte certainement… En revanche ceux qui doivent écrire des librairies réutilisables auront tout intérêt à coder en pensant à cette possibilité pour simplifier l’utilisation de leur code.

Et les interfaces ?

Le principe est le même. Et comme je le disais la plupart des utilisations se feront dans des librairies de code, comme le Framework l’est lui-même. Ainsi, le Framework 4.0 définit déjà de nombreuses interfaces supportant covariance et contravariance. IEnumerable<T> permet la covariance de T, IComparer<T> supporte la contravariance de T, etc. Dans la plupart des cas vous n’aurez donc pas à vous souciez de tout cela.

Lien

La documentation est pour l’instant assez peu fournie, et pour cause, tout cela est en bêta ne l’oublions pas. Toutefois la sortie de VS2010 et de .NET 4.0 est prévue pour Mars 2010 et le travail de documentation a déjà commencé sur MSDN. Vous pouvez ainsi vous référer à la série d’articles sur MSDN : Covariance and Contravariance.

Conclusion

Les nouveautés de C# 4.0, qui peuvent toujours changer dans l’absolu puisque le produit est encore en bêta, ne sont pas à proprement parler des évolutions fortes du langage. On voit bien que les 3 premières versions ont épuisé le stock de grandes nouveautés hyper sexy comme les génériques ou Linq qui ont modifié en profondeur le langage et décuplé ses possibilités.

C# 4.0 s’annonce comme une version mature et stable, un palier est atteint. les nouveautés apparaissent ainsi plus techniques, plus “internes” et concernent moins le développeur dans son travail quotidien.

Une certaine convergence avec F# et le DLR pousse le langage dans une direction qui ouvre la polémique. Je suis le premier a resté dubitatif sur l’utilité d’une telle évolution surtout que la sortie de F# accompagnera celle de C# 4.0 et que les passionnés qui veulent à tout prix coder dans ce style pourront le faire à l’aide d’un langage dédié. Mélanger les genre ne me semble pas un avantage pour C#.

C# est aujourd’hui mature et il est peut-être temps d’arrêter d’y toucher…
L’ensemble .NET est d’ailleurs lui-même arrivé à un état de complétude qu’aucun framework propriétaire et cohérent n’avait certainement jamais atteint.
.NET a tout remis à plat et à repousser les limites sur tous les fronts.

On peut presque affirmer que .NET est aujourd’hui “complet”. Même si la plateforme va encore évoluer dans l’avenir. Mais tous les grands blocs sont présent, des communications à la séparation code / IHM, des workflows aux interfaces graphiques et multitouch, de LINQ au Compact Framework.

Quand un système arrive à un haut niveau de stabilité, le prochain est déjà là, sous notre nez mais on le sait pas. Le palier atteint par .NET 4.0 marque une étape importante. Cet ensemble a coûté cher, très cher à développer. Il s’installe pour plusieurs années c’est une évidence (et une chance !). Mais on peut jouer aux devinettes : quelle sera la prochaine grande plateforme qui remplacera .NET, quel langage remplacera C# au firmament des langages stars pour les développeurs dans 10 ans ?

Bien malin celui qui le devinera, mais il est clair que tout palier de ce type marque le sommet d’une technologie. De quelle taille est le plateau à ce sommet ? Personne ne peut le prédire, mais avec assurance on peut affirmer qu’après avoir grimpé un sommet, il faut le redescendre. Quelle sera la prochaine montagne à conquérir ? Il y aura-t-il un jour un .NET 10 ou 22 ou bien quelque chose d’autre, de Microsoft ou d’un autre éditeur, l’aura-t-il supplanté ?

C’est en tout cas une réalité qui comme l’observation des espaces infinis qu’on devine dans les clichés de Hubble laisse songeur…

Rêver bien, mais surtout et pour les dix ans à venir : Stay Tuned !

Quelques conseils de design (UserControl, Blend, Visual State manager, Silverlight / WPF)

L’une des avancées les plus intéressantes introduite dans Silverlight 2 (puis reprise sous WPF et naturellement sous Silverlight 3) est très certainement le Visual State Manager. Gestionnaire des états visuels simplifiant la conception visuelle des contrôles (UserControl). Bien utiliser le VSM, outre de rendre plus simple la représentation des états visuels d’un composant, apporte aussi une clarification essentielle à la gestion des transitions entre ces derniers.Plus...