Dot.Blog

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

Parallel FX, P-Linq et maintenant les Reactive Extensions…

[new:10/8/2010]Les Parallel Extensions, connues jusqu’à lors sous le nom de Parallel Framework Extensions (ou PFX) forment une librairie permettant de faciliter la construction d’algorithmes parallèles (multi-thread) tirant partie des machines multi-cœur. Je vous en avais déjà parlé, ainsi que de P-Linq les extensions parallèles pour LINQ. Deux choses importantes à savoir aujourd’hui : les Parallel Extensions font partie de .NET 4 (VS 2010, Silverlight…) et une nouvelle librairie arrive, les Reactive Extensions !Plus...

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 !

Silverlight : Enum.GetValues qui n'existe pas, binding et autres considérations

Silverlight, comme vous le savez, propose dans quelques méga octets un mini framework .NET qui est tellement bien "découpé" que la plupart du temps on ne se rend même pas compte qu'il manque des dizaines de méga de code binaire... Pourtant entre une installation complète du Framework 3.5 ou 4.0 à venir et l'installation du plugin Silverlight il y a comme une énorme différence ! De deux choses l'une, soit Silverlight ne sait rien faire tellement il est diminué par ce découpage, ce qui n'est pas le cas, soit le Framework complet est juste gonflé avec des fichiers inutiles pour "faire sérieux", ce qui n'est pas le cas non plus :-)

Un découpage savant 

Donc ce n'est ni l'un ni l'autre. La troisième possibilité, qui est la bonne réponse, est que le Framework complet est d'une richesse infinie dans les moindres détails, et que le Framework Silverlight "ruse" en zappant beaucoup de détails mais sans perdre l'essentiel. Cela donne l'impression qu'on peut tout faire en Silverlight comme en WPF. C'est "presque" vrai. Assez rapidement on tombe sur les fameux petits détails qui manquent. Cela implique de les compenser par du code.

Cela dit loin d'être une critique négative de Silverlight s'en est au contraire une apologie ! Je trouve en effet le découpage savant qui a été effectué dans Silverlight particulièrement bien fait. L'approche est très sensée : il est rarissime (même impossible) qu'une application utilise et nécessite d'accéder à toutes les méthodes, toutes les propriétés de toutes les classes du Framework tellement celui-ci est riche. Du coup, en faisant des coupes bien choisies, on peut laisser les squelettes de presque tout le Framework ainsi que les principales méthodes et propriétés utilisées le plus souvent. On obtient le Framework Silverlight dans lequel un code standard trouvera 95% de tout ce qu'il lui faut pour tourner. Beaucoup d'applications simples ne verront même pas qu'il manque quelque chose. En revanche pour les autres cas, le développeur ajoutera les contournements nécessaires ce qui grossira un peu son code binaire Silverlight, mais d'une petite fraction très supportable. Rien à voir avec le coût d'une installation du Framework complet.

Il n'en reste pas moins vrai que parfois pour des choses très simples on se retrouve un peu le bec dans l'eau. "Tiens, c'est bizarre, j'aurais juré que la méthode xxx existait !" Et non, on n'a pas rêvé, elle existe bien, mais dans le Framework complet, pas dans celui de Silverlight. Un exemple tout simple : la méthode GetValues() de la classe Enum.

Enum.GetValues où es tu ?

Un cas d'utilisation très basique qui fait voir immédiatement qu'il manque quelque chose : essayez de faire un binding entre une combobox et une énumération. Truc classique par excellence.

Que le binding soit fait par Xaml ou bien par code, à un moment où un autre il faut que l'énumération sache retourner la liste de ses valeurs. C'est justement la fonction de la méthode Enum.GetValues().

Mais dans le Framework Silverlight cette méthode n'existe tout simplement pas. Victime de la cure d'amaigrissement évoquée plus haut. Il ne s'agit donc ni d'un oubli ni d'un dommage collatéral, c'est un parti pris, assumé.

Et alors on fait comment ?

Assumé, assumé... comme il y va ! Non, je vous l'assure, c'est assumé. Par l'équipe qui développe le Framework Silverlight en tout cas. Mais pas forcément par les développeurs qui l'utilisent ! Car pour eux c'est une autre histoire puisque, en effet, il va falloir réinventer cette méthode.

A l'aide d'un peu de Linq to Object et de Reflexion, on peut s'en sortir.

Linq et Reflexion

Il existe en effet un moyen d'obtenir les valeurs d'une Enum par la réflexion, en utilisant la méthode GetFields() sur le type de l'Enum dont on souhaite obtenir les valeurs. 

GetFields() retourne un tableau de FieldInfo. Une Enum présente alors ses différentes valeurs comme un tableau de champs. En plus de ces champs, GetFields() retournera aussi des éléments qui ne sont pas des valeurs de l'énumération mais d'autres champs de la classe. Au sein de FieldInfo vous trouverez un ensemble de méthodes nommées Isxxx(), l'une d'entre elles nous intéresse plus particulièrement ici; c'est IsLiteral. Toutes les valeurs de l'énumération retournent True. La solution est alors simple en ajoutant à la Réflexion un peu de Linq to Object :

   1:  var enumType = typeof(monEnumeration);
   2:  var fields =  from field in enumType.GetFields()
   3:                where field.IsLiteral
   4:                select field.GetValue(null).ToString();
   5:  LaCombobox.ItemsSource = fields;

A partir du type de l'énumération (ligne 1) on construit une requête Linq to Object qui filtre tous les champs ayant IsLiteral à vrai et qui sélectionne la valeur de ce champ sous la forme d'une string.

Ne reste plus qu'à faire le binding entre cette requête Linq et ItemsSource de la combo box.

Il faudra ajouter un peu de code pour transformer la chaîne sélectionnée dans la combo en une valeur de l'énumération grâce à un appel à Enum.Parse().

C'est la version simple et courte. Bien entendu dans le cas où on souhaite faire du binding plus automatisé, notamment directement en Xaml, la solution donnée ici est un peu trop simple. L'esprit est le bon mais il manque des petites choses comme un convertisseur de valeurs.

D'autres versions plus sophistiquées

Il est bien sûr possible d'aller plus loin et de formuler une solution plus sophistiquée qui permettent de faire du binding en Xaml notamment. Je vous laisse y réfléchir, ça fait un bon excercice C# et ce n'est pas un MVP C# qui vous dira que s'entraîner mentalement de la sorte sur le langage est inutile ! :-)

Mais sachez que d'autres y ont déjà pensé et ont proposé des solutions souvent assez proches (ce problème ne peut pas être résolu de dix milles façons). En voici quelques unes dans lesquelles vous pourrez piocher matière à aller plus loin :

Silverlight c'est sympa et en plus ça fait travailler les méninges, que du bon ! 

Stay Tuned !

 

 

 

XML/XAML pretty printer gratuit

Il arrive souvent que du code XML soit produit "au kilomètre" sans mise en forme particulière. Même si Internet Explorer sait afficher un tel fichier en le mettant en forme automatiquement, on souhaite parfois disposer d'une version formatée lisible par un humain.

ODPrettyXml, un utilitaire console très simple qui ne fait que ça... Il traite les fichiers XML, mais aussi du XAML sans souci. Toutefois vous remarquerez que ODPrettyXml travaille toujours sur un fichier de sortie différent de l'original, certaines transformations pourraient avoir des effets non souhaités. L'utilitaire est donc avant tout conçu comme un "pretty printer" dont la vocation est de rendre le document plus lisible pour un humain. Les fichiers produits, même s'ils restent fonctionnels, n'ont pas vocation a être utilisé en programmation.

Pour le mode d'emploi, tapez ODPrettyXml sous console, l'aide sera affichée. Le programme ne demandant aucune saisie, il est possible de l'utiliser dans des fichiers de commandes (ou des batchs).

La syntaxe la plus habituelle est "ODPrettyXml <nom du fichier>" qui fabriquera automatique un fichier de sortie de même nom se terminant par "pretty" suivi de l'extension du fichier original (par exemple: toto.xml donnera toto.pretty.xml).

Si vous tapez "ODPrettyXml ?" vous obtiendrez la liste de tous les encoders connus et utilisables avec leur code page. C'est le nom qu'il faut utiliser en 3eme paramètre de ODPrettyXml. Par exemple pour utiliser unicode il faut taper "ODPrettyXml <source> <sortie> utf-16". Quand un encodeur est spécificé, il faut aussi saisir le nom du fichier de sortie (2d paramètre).

Dernière remarque, ODPrettyXml ne fait qu'encoder le fichier et le mettre en forme avec des indentations, notamment il ne contrôle pas si l'encodage demandé est conforme à celui déclaré dans le fichier source. Un fichier indiquant qu'il est codé en UTF-8 peut être encodé en UTF-16, son entête indiquera toujours UTF-8, le fichier n'est pas modifié par ODPrettyXml.

Téléchargement : odPrettyXml.exe (34,00 kb)
(exécutable .NET 3.5, mode console)
(projet VS 2008 complet. Le fichier de signature électronique est absent vous devrez en créer un autre).
Amusez-vous bien !
Et Stay Tuned pour d'autres nouvelles (notamment un gros article à venir les Splash screen sous Silverlight !)
(PS: l'aide du logiciel a quelques coquilles, à vous de les trouver et les corriger :-) )

Les propriétés de dépendance et les propriétés jointes sous WPF (article à télécharger)

En voilà un beau sujet ! Vous allez me dire qui irait investir deux jours à taper 25 pages sur ce sujet, il faut être totalement givré ! Et bien vous en avez un devant vous (par blog interposé) ... donc pas de remarques désobligeantes sur ma santé mentale, hein !

Certes les propriétés de dépendance et les propriétés jointes de WPF et Silverlight ne semblent pas être un sujet aussi exitant que quelques astuces LINQ ou la meilleure façon d'intégrer de la 3D dans Silverlight 3 (miam!)... Je vous le concède. Mais en revanche c'est un sujet capital car derrière ces propriétés bien particulières se cache l'un des piliers de la puissance de WPF, un mécanisme qui autorise la gestion des styles, des animations, du Data Binding et de bien d'autres choses sans lesquelles WPF ne serait pas ce qu'il est.

Savoir ce qu'est une propriété de dépendance ou une propriété jointe, savoir en déclarer et savoir les utiliser représente une base impossible à zapper.

Alors, pour tout savoir sur le sujet, téléchargez mon dernier article "Propriétés de dépendance et propriétés jointes (WPF/Silverlight)" !

L'article est fourni en PDF avec les sources du projet exemple (utilisable sous VS 2008 ou Blend 2).

NB: Pour mieux comprendre cet article si vous n'êtes pas encore un expert de WPF, je vous conseille la lecture de mon précédent article "10 bonnes raisons de préférer WPF".

Bonne lecture,

... Et Stay Tuned !

Visual NDepend un outil d'analyse de code original et puissant

A partir d'une certaine taille le code d'une application devient difficile à maintenir même s'il est bien conçu. De même, la maintenance évolutive oblige souvent à mettre les mains dans un code écrit par d'autres (si ce code est assez ancien, on peut s'y perdre même en en étant l'auteur d'ailleurs !). Auditer un code est ainsi un besoin fréquent dans le travail de tous les jours d'un développeur. Si on effectue des missions d'audit ce besoin devient alors impérieux car il est nécessaire d'entrer rapidement dans un code "étranger" et toujours complexe (je n'ai jamais été appelé en audit pour des logiciels de 20 lignes...).

Les outils d'analyse de code sont ainsi des compagnons indispensables. Hélas ils ne sont pas si nombreux que ça et comme tout ce qui touche la qualité, ils sont un peu "oubliés".

Je voulais ainsi vous présenter un excellent outil : Visual NDepend.

NDepend est puissant, il permet de construire et visualiser des comparaisons, d'auditer la qualité du code, et surtout de mieux visualiser la structure d'une application. Le tout quantitavement (métriques), qualitativement (l'information est pertinente) et graphiquement (le logiciel propose de nombreux diagrammes et schémas permettant de visualiser ce qui est invisible en lisant le code source : structures, dépendances, ...).

NDepend ne se limite pas seulement à l'audit de code, c'est aussi un excellent outil de refactoring.

On notera aussi, côté puissance, l'intégration d'un langage d'interrogation, le CQL (Code Query Language), permettant à la façon de SQL de "questionner" un projet et son code comme s'il s'agissait d'une base de données. La comparaison s'arrête là avec SQL car CQL "comprend" le code alors que SQL ne comprend pas le sens des données qu'il manipule. Cette grande différence confère à NDepend une très grande souplesse et beaucoup d'intelligence.

Par exemple il est très simple de faire des requêtes du type "montre moi toutes les méthodes privées dont le nombre de lignes de code est supérieur à 20", très utile pour repérer rapidement dans un projet les lourdeurs ou la nécessité de refactoring. Une telle interrogation se formule en la requête CQL suivante "SELECT METHODS WHERE NbLinesOfCode > 20 AND IsPrivate".
On peut bien entendu créer des requêtes plus sophistiquées comme rechercher toutes les types qui sont une définition de classe et qui implémentent telle ou telle autre interface !

NDepend est ainsi un outil de grande qualité dont on ne peut que conseiller l'utilisation. Réalisé par Patrick Smacchia, un acteur connu de la scène .NET et Microsoft MVP C#, NDepend est vendu 299 euros en licence 1 utilisateur, c'est à dire pas grand chose au regard des immenses services qu'il peut rendre.

Il y a beaucoup de choses à dire sur cet outil et surtout à voir. Le mieux si vous êtes intéressés est de vous rendre sur le site du logiciel www.ndepend.com où vous pourrez visionner de nombreuses petites vidéos qui vaudront mieux que de longs discours.

Bon refactoring !

 

MEF - Managed Extensibility Framework - De la magie est des plugins !

Une gestion de plugin simplifiée 

Actuellement encore en preview mais très utilisable depuis la Preview 2 du mois d’octobre, MEF est un nouveau procédé de gestion des plugins pour le Framework .NET.

Projet Open Source se trouvant sur CodePlex (http://www.codeplex.com/MEF) MEF facilite l’implémentation des addins (ou plugins) en automatisant la liaison entre les propriétés du programme qui importe des valeurs et les addins qui exportent les valeurs. Sachant que tout module peut être importateur et exportateur à la fois, permettant des chaînes de addins eux-mêmes supportant des addins…

MEF et les autres

Microsoft a intégré dans le Framework 3.5 une gestion des plugins  qui se base sur l’espace de nom System.Addin. L’approche est différente de MEF et le choix n’est pas évident entre ces deux solutions.  D’autant qu’il en existe une troisième ! En effet, Microsoft a aussi publié le Composite Application Guidance for WPF, spécifiquement dédié aux applications de ce type donc, dont la dernière version date de juin…

MEF est utilisable aussi sous WPF, même sous Silverlight mais je n’ai pas encore testé cet aspect là.

Comment choisir ?

Personnellement l’approche de MEF me convient très bien, c’est assez simple et cela répond aux besoins d’une gestion de plugins (ou addins). En ces temps d’avalanche de technologies toutes plus alléchantes les unes que les autres chez Microsoft il est vrai que je suis assez tenté par la simplicité de MEF qui évite de trop s’encombrer les neurones déjà bien saturés ! Simple et complet, je préfère donc MEF, mais je suis convaincu que dans certains cas la solution spécifique à WPF est mieux adaptée ou que System.Addin apporte certains petits plus (sécurité par exemple). J’avoue bien humblement que je n’ai pas encore trouvé le temps de tester à fond System.Addin ni la solution WPF. A vous de voir donc, et le mieux c’est de regarder de près en testant chaque approche. Ici je vais vous parler de MEF, pour les autres solutions suivez les liens suivants :

Composite Applicationn Guidance for WPF (http://msdn.microsoft.com/en-us/library/cc707819.aspx)

Pour System.Addin je vous conseille les 12 billets de Jason He qui sont plus parlant que l’aride documentation de l’espace de nom sur MSDN. (http://blogs.msdn.com/zifengh/archive/2007/01/04/addin-model-in-paint-net-1-introduction.aspx)

MEF – Le principe

Le but est de simplifier l’écriture d’applications dites  extensibles. MEF automatise la découverte des modules (les plugins) ainsi que la composition des valeurs, c'est-à-dire un lien automatique entre les valeurs exportées et le module importateur. De prime abord c’est pas forcément très clair, mais le code qui va venir va vous éclairez (je l’espère en tout cas !). En première approximation disons que la composition dans MEF est une sorte de Databinding qui relie une propriété déclarée dans l’importateur à une ou plusieurs valeurs du ou des modules exportateurs (les plugins).

MEF – Les avantages

MEF est assez simple, je l’ai dit, et c’est un gros avantage (mais pas simpliste, nuance). Il est Open Source c’est un plus. Mais surtout MEF évite de réinventer la poudre à chaque fois qu’on désire implémenter une gestion de plugins. Et toute application moderne se doit d’être extensible ! Qu’il s’agisse d’applications à usage interne ou bien de logiciels d’éditeur, c’est souvent l’extensibilité et les plugins qui font le succès d’une application, ou aide grandement à celui-ci. Disposer d’une solution fiable pour résoudre ce problème d’architecture très courant est donc l’avantage principal de MEF.

Les extensions créées avec MEF peuvent être partagées par plusieurs applications, elles peuvent elles-mêmes utiliser des extensions et MEF saura les charger dans le bon ordre automatiquement.

MEF propose un ensemble de service de découverte simplifiant la localisation et le chargement des extensions. On trouve aussi un système de lazzy loading et un mécanisme de métadonnées riches permettant aux plugins d’informer l’hôte sur sa nature ou transmettre des données complémentaires.

Un Exemple ! Un Exemple !

Bon, je ne vais pas refaire la doc de MEF, qui n’existe pas d’ailleurs (enfin si mais c’est encore très partiel), et pour illustrer le propos je vais expliquer le fonctionnement de MEF au travers d’un exemple le plus simple possible (ce billet s’annonce déjà bien long !).

Installer MEF

Avant toute chose, et pour faire tourner l’exemple (et vous amuser avec MEF), il faut que vous installiez MEF. Rassurez-vous, c’est très simple, sur le site de MEF (indiqué en introduction) vous trouverez dans l’onglet Releases le dernier zip à télécharger. Il suffit de prendre le contenu du zip et de le copier quelque part sur votre disque. C’est tout.  Le source est fourni ainsi que la lib compilée. Il suffit dans une application d’ajouter une référence à la DLL « System.ComponentModel.Composition.dll » se trouvant le répertoire /Bin du fichier téléchargé et l’affaire est jouée. Un Using de System.ComponentModel.Composition sera nécessaire dans l’application hôte ainsi que dans les applications fournissant un service (les DLL des plugins).

L’application exemple

Je vais faire très simple comme annoncé : prenons une application console. Cette application doit appliquer des calculs à des valeurs. Tous les algorithmes de calcul seront des plugins. Algorithme est un bien grand mot puisque dans cet exemple j’implémenterai l’addition et la multiplication. Dans la réalité les plugins découverts seraient par exemple ajoutés à un menu ou une toolbar. Ici nous nous contenterons de les lister à la console et de les appeler sur des valeurs codées en dur (dans une vraie calculette les valeurs seraient saisies par l’utilisateur).

Le contrat

Le plus intelligent pour une gestion de plugin est bien entendu d’avoir une ou plusieurs interfaces décrivant ce que sait faire un plugin. C’est le contrat (ou les contrats) entre l’hôte et ses plugins.

Ainsi nous allons ajouter à notre solution un projet de type DLL pour décrire cette interface. Cela se fait dans un projet séparé puisque l’interface doit être techniquement connu à la fois de l’hôte et des plugins et qu’il faut éviter à tout prix l’existence de dépendances entre ces deux niveaux de l’architecture. De plus l’interface peut ainsi être diffusée avec l’exécutable pour que des tiers puissent écrire des plugins.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace MEFInterface
{
    public interface ICompute
    {
        double Compute(double x, double y);
        string OperationName { get; }
    }
}

Le code ci-dessus est très simple. Comme on le voit ce projet DLL ne fait aucune référence à MEF ni à notre application ni à rien d’autre d’ailleurs (en dehors du Framework). L'interface ICompute expose une méthode Compute() et une propriété de type chaîne OperationName. Compute réalise le calcul sur les deux valeurs passées en paramètre et OperationName retourne le nom de l'algorithme pour que l'hôte puisse éventuellement fabriquer un menu listant tous les plugins installés.

Les plugins

Cela peut sembler moins naturel de commencer par les plugins que par l’application hôte mais je pense que vous comprendrez mieux dans ce sens là. Donc comment implémenter un plugin ?

Nous ajoutons à notre solution un nouveau projet de type librairie de classes qui sera faite d’une seule classe implémentant l’interface que nous venons de voir. Prenons l’exemple de la DLL de l’addition, sachant que celle gérant la multiplication est identique (au calcul près) et que nous pourrions créer ainsi une ribambelle de projets implémentant autant d’algorithmes de calculs que nous en voulons.

Le projet ModuleAddition contient ainsi un fichier Addition.cs, fichier contenant le code suivant :

(code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition; // MEF
using MEFInterface; // interface des plugins
 
namespace ModuleAddition
{
 
    [Export(typeof(ICompute))] // exporter la classe vue sous l'angle de l'interface partagée
    public class Addition : ICompute
    {
        // Réalise l'addition de deux doubles.
        public double Compute(double x, double y)
        {
            return x + y;
        }
 
        public string OperationName { get { return "Addition"; } }
 
    }
}

Ce code est redoutablement simple et n’a pas grand-chose de spécial. Il implémente l’interface que nous avons définie (d’où le using à MEFInterface, nom de notre projet interface, rien à voir avec un module de MEF donc, et la référence ajoutée cette DLL).

Ce qui est spécifique à MEF se résume à deux choses : d’une part le using de System.ComponentModel.Composition qui implique l’ajout dans les références de la DLL de MEF et d’autre part l’attribut Export qui décore la classe Addition (implémentant l’interface ICompute que nous avons créée).

L’attribut Export possède des variantes, ici l’usage que nous en faisons indique tout simplement à MEF que la classe en question est un fournisseur de service plugin et qu’il faut la voir non pas comme une classe mais comme l’interface ICompute. C’est un choix, il peut y en avoir d’autres, mais concernant une gestion de plugin cette approche m’a semblé préférable.

Concernant le code de la classe Addition on voit qu’elle implémente les éléments de notre interface donc la méthode Compute qui retournera ici l’addition des deux paramètres. Dans la classe Multiplication (l’autre plugin non visible ici) c’est la même chose, sauf que Compute calcule la multiplication. La propriété OperationName de l’interface est implémentée en retournant le nom de l’algorithme de calcul exposé par le plugin. Ce nom sera utile à l’hôte pour créer un bouton, une entrée de menu, etc.

On notera que MEF supporte la notion de métadonnées. Il existe ainsi des attributs permettant de décorer une classe en ajoutant autant de couple clé / valeur qu’ont le souhaite. Le nom du plugin, sa version, le nom de l’auteur et bien d’autres données peuvent ainsi être transmis à l’hôte via ce mécanisme que je ne démontre pas ce billet.

Pour simplifier les tests notons que j’ai modifié les propriétés du projet de chaque plugin pour qu’en mode debug les DLL soient directement placées dans le répertoire de debug de l’application hôte. Jai choisi ici aussi la simplicité : les plugins doivent être dans le répertoire de l’exécutable. Bien entendu dans la réalité vous pouvez décider de créer un sous-répertoire à votre application pour les plugins, ce qui est même plus propre.

L’hôte

Le voici ! Application en mode console (quel mode merveilleux pour les tests !), son fonctionnement est rudimentaire : nous voulons que l’application découvre grâce à MEF tous les plugins installés et que pour chacun elle affiche le nom de l’opération et effectue un appel au calcul correspondant. Les valeurs servant à ces derniers sont figées dans le programme, pas question d’ajouter une gestion de saisie utilisateur dans cette démo.

Les using et références

Pour fonctionner le programme doit faire référence à la fois à la MEF et au projet interface (aux projets interfaces si nous supportions plusieurs types de plugins par exemple).

Ces références trouvent leur pendant dans les using :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition; // MEF
using MEFInterface;
using System.IO; // interface des addins

La section Main

Elle aura pour rôle de créer une instance de l’objet programme et d’appeler sa méthode Run (nom arbitraire bien entendu). Rien de spécial à comprendre donc.

static void Main(string[] args)
{
   new Program().Run();
}

La propriété importée

Nous avons vu que les plugins exportent une (ou plusieurs) valeur(s), dans notre exemple il s’agit d’instances de classes supportant l’interface ICompute. Du côté de l’hôte il faut « quelque chose » pour importer ces valeurs.

C’est le rôle de la propriété  Operations dans notre code :

[Import] // attribut marquant une propriété importée depuis les addins découverts
        public IEnumerable<ICompute> Operations { get; set; }

Comme vous le voyez cette propriété est une liste. Cela s’explique par le fait que nous supportons plusieurs plugins à la fois. Dans le cas contraire (un seul plugin) la propriété aurait été directement du type de l’interface ICompute. Un seul plugin peut sembler étrange mais cela peut correspondre à une partie spécifique de votre application que vous désirez pouvoir personnaliser à tout moment en fournissant simplement une nouvelle DLL qui écrasera celle installée chez les utilisateurs. Un mode d’utilisation à ne pas négliger… Mais ici nous supportons plusieurs plugins à la fois et notre propriété est ainsi une liste.  Autre spécificité liée à la MEF, la propriété est décorée par l’attribut Import qui indique à MEF qu’il devra faire le binding entre la propriété et les plugins supportant le type de celle-ci.

Le code du Run

L’application se déroule ici. Nous commençons par appeler une méthode Compose() dont le rôle sera justement de mettre en route toute la magie de MEF. Nous verrons cela plus bas. Ensuite nous ne faisons que boucler sur la collection représentée par notre propriété comme si nous y avions déjà placé du contenu et nous l’utilisons « normalement » c'est-à-dire comme si MEF n’existait pas. Rien de bien sorcier là non plus, un foreach énumère toutes les entrées (des instances vues comme des interfaces ICompute) et utilise les méthodes et les propriétés accessibles (le nom du plugin et son unique méthode Compute).

private void Run()
{
    Compose();
    Console.WriteLine("Operation Addins detected (test on x=5 and y=6) :");
    foreach (var operation in Operations)
    {
        Console.WriteLine(operation.OperationName+" : "+operation.Compute(5,6));
    }
    
    Console.ReadLine();
}

Comme vous le constatez, il n’y aurait pas MEF que nous aurions écrit le même code, sauf que nous trouverions quelque part, à la place de l’appel à Compose que nous allons voir maintenant, un code qui aurait « rempli » la propriété Operations en créant des instances de classes supportant ICompute.

Le code de Compose

Ecrire un bout de code qui créée des instances de classes supportant ICompute et remplir la liste Operations (la propriété liste de notre application), c’est ce que fait la méthode Compose. Mais c'est en réalité c’est MEF qui va le faire pour nous, et mieux encore, il va aller chercher tout ça dans des DLL de plugins.

Regardons le code de Compose :

private void Compose()
{
    // création du catalogue. Les addins sont dans le répertoire de l'exe
    var catalog = new DirectoryPartCatalog(System.Reflection.Assembly.GetExecutingAssembly().Location);
    // créé le conteneur
    var container = new CompositionContainer(catalog.CreateResolver());
    // ajoute this pour qu'il puisse être lié via ses attributs 'import'
    container.AddPart(this); 
    // réalise la composition (connexion de tous les exports à tous les imports)
    container.Compose();
}

4 lignes. 4 lignes seulement pour : 

  1. créer un catalogue de plugins d’après un répertoire disque,
  2. créer un conteneur qui est le point de fusion où se rencontre les exportateurs et les importateurs,
  3. ajouter l’instance de notre application au conteneur (puisqu’elle est consommatrice via sa propriété marquée Import), d) demander au conteneur d’effectuer le binding entre tous les exportateurs et tous les importateurs. Incroyable non ?

Et c’est tout !

Ce qui va se passer au lancement de l’application est assez simple : MEF va balayer tout le répertoire indiqué dans le catalogue (qui peut contenir plusieurs répertoires), chercher les DLL et tester pour chacune celles qui exportent des éléments compatibles avec les importations. Qu’il s’agisse ici de notre application (consommatrice via son Import) ou même des plugins entre eux qui peuvent aussi avoir des relations consommateur / fournisseur.

C’est là que réside la magie de MEF qui va ensuite faire le nécessaire pour renseigner automatiquement toutes les propriétés ayant l’attribut Import.  Dans notre cas MEF va détecter que deux DLL sont compatibles avec l’Import de notre application. Il va créer des instances des classes supportant l’interface ICompute, en faire une liste qui sera placée dans la propriété Operations automatiquement.

Il ne reste plus à notre application qu’à utiliser la propriété Operations comme n’importe quelle autre propriété, sauf qu’ici son contenu a été créé par MEF automatiquement.

Conclusion

Simple et un peu magique, c’est ça MEF.

MEF règle un problème récurrent d’architecture, l’extensibilité, de façon simple et élégante. Bien entendu MEF permet de faire plus choses que ce que j’en montre ici. A vous de le découvrir en le téléchargeant et en étudiant les exemples et le début de documentation fourni !

Le code du projet VS2008 : MEFExample.zip (16,08 kb)

Contourner le problème de l'appel d'une méthode virtuelle dans un constructeur

Ce problème est source de bogues bien sournois. J'ai déjà eu l'occasion de vous en parler dans un billet cet été (Appel d'un membre virtuel dans le constructeur ou "quand C# devient vicieux". A lire absolument...), la conclusion était qu'il ne faut tout simplement pas utiliser cette construction dangereuse. Je proposais alors de déplacer toutes les initialisations dans le constructeur de la classe parent, mais bien entendu cela n'est pas applicable tout le temps (sinon à quoi servirait l'héritage).

Dès lors comment proposer une méthode fiable et systématique pour contourner le problème proprement ?

Rappel

Je renvoie le lecteur au billet que j'évoque en introduction pour éviter de me répéter, le problème posé y est clairement démontré. Pour les paresseux du clic, voici en gros le résumé de la situation : L'appel d'une méthode virtuelle dans le constructeur d'une classe est fortement déconseillé. La raison : lorsque qu'une instance d'une classe dérivée est créée elle commence par appeler le constructeur de son parent (et ainsi de suite en cascade remontante). Si ce constructeur parent appelle une méthode virtuelle overridée dans la classe enfant, le problème est que l'instance enfant elle-même n'est pas encore initialisée, l'initialisation se trouvant encore dans le code du constructeur parent. Et cela, comme vous pouvez l'imaginer, ça sent le bug !

Une première solution

La seule et unique solution propre est donc de s'interdire d'appeler des méthodes virtuelles dans un constructeur. Et je serai même plus extrémiste : il faut s'interdire d'appeler toute méthode dans le constructeur d'une classe, tout simplement parce qu'une méthode non virtuelle, allez savoir, peut, au gréé des changements d'un code, devenir virtuelle un jour. Ce n'est pas quelque chose de souhaitable d'un pur point de vue méthodologique, mais nous savons tous qu'entre la théorie et la pratique il y a un monde...

Tout cela est bien joli mais s'il y a des appels à des méthodes c'est qu'elles servent à quelque chose, s'en passer totalement semble pour le coup tellement extrême qu'on se demande si ça vaut encore le coup de développper ! Bien entendu il existe une façon de contourner le problème : il suffit de créer une méthode publique "Init()" qui elle peut faire ce qu'elle veut. Charge à celui qui créé l'instance d'appeler dans la foulée cette dernière pour compléter l'initialisation de l'objet.

Le code suivant montre une telle construction :

 // Classe Parent
    public class Parent2
    {
        public Parent2(int valeur)
        {
            // MethodeVirtuelle();
        }
 
        public virtual void Init()
        {
            MethodeVirtuelle();
        }
 
        public virtual void MethodeVirtuelle()
        { }
    }
 
    // Classe dérivée
    public class Enfant2 : Parent2
    {
        private int val;
 
        public Enfant2(int valeur)
            : base(valeur)
        {
            val = valeur;
        }
 
        public override void MethodeVirtuelle()
        {
            Console.WriteLine("Classe Enfant2. champ val = " + val);
        }
    }

La méthode virtuelle est appelée dans Init() et le constructeur de la classe de base n'appelle plus aucune méthode.

C'est bien. Mais cela complique un peu l'utilisation des classes. En effet, désormais, pour créer une instance de la classe Enfant2 il faut procéder comme suit :

 // Méthode 2 : avec init séparé
 var enfant2 = new Enfant2(10);
 enfant2.Init(); // affichera 10

Et là, même si nous avons réglé un problème de conception essentiel, côté pratique nous sommes loin du compte ! Le pire c'est bien entendu que nous obligeons les utilisateurs de la classe Enfant2 à "penser à appeler Init()". Ce n'est pas tant l'appel à Init() qui est gênant que le fait qu'il faut penser à le faire ... Et nous savons tous que plus il y a de détails de ce genre à se souvenir pour faire marcher un code, plus le risque de bug augmente.

Conceptuellement, c'est propre, au niveau design c'est à fuir...

Faut-il donc choisir entre peste et choléra sans aucun espoir de se sortir de cette triste alternative ? Non. Nous pouvons faire un peu mieux et rendre tout cela transparent notamment en transférant à la classe enfant la responsabilité de s'initialiser correctement sans que l'utilisateur de cette classe ne soit obligé de penser à quoi que ce soit.

La méthode de la Factory

Il faut absolument utiliser la méthode de l'init séparé, cela est incontournable. Mais il faut tout aussi fermement éviter de rendre l'utilisation de la classe source de bugs. Voici nos contraintes, il va falloir faire avec.

La solution consiste à modifier légèrement l'approche. Nous allons fournir une méthode de classe (méthode statique) permettant de créer des instances de la classe Enfant2, charge à cette méthode appartenant à Enfant2 de faire l'intialisation correctement. Et pour éviter toute "bavure" nous allons cacher le constructeur de Enfant2. Dès lors nous aurons mis en place une Factory (très simple) capable de fabriquer des instances de Enfant2 correctement initialisées, en une seule opération et dans le respect du non appel des méthodes virtuelles dans les constructeurs... ouf !

C'est cette solution que montre le code qui suit (Parent3 et Enfant3 étant les nouvelles classes) :

    // Classe Parent
    public class Parent3
    {
        public Parent3(int valeur)
        {
            // MethodeVirtuelle();
        }
 
        public virtual void Init()
        {
            MethodeVirtuelle();
        }
 
        public virtual void MethodeVirtuelle()
        { }
    }
 
    // Classe dérivée
    public class Enfant3 : Parent3
    {
        private int val;
 
        public static Enfant3 CreerInstance(int valeur)
        {
            var enfant3 = new Enfant3(valeur);
            enfant3.Init();
            return enfant3;
        }
 
        protected Enfant3(int valeur)
            : base(valeur)
        {
            val = valeur;
        }
 
        public override void MethodeVirtuelle()
        {
            Console.WriteLine("Classe Enfant3. champ val = " + val);
        }
    }

La création d'une instance de Enfant3 s'effectue dès lors comme suit :

 var enfant3 = Enfant3.CreerInstance(10);

C'est simple, sans risque d'erreur (impossible de créer une instance autrement), et nous respectons l'interdiction des appels virtuels dans le constructeur sans nous priver des méthodes virtuelles lors de l'initialisation d'un objet. De plus la responsabilité de la totalité de l'action est transférée à la classe enfant ce qui centralise toute la connaissance de cette dernière en un seul point.

Dans une grosse librairie de code on peut se permettre de déconnecter la Factory des classes en proposant directement une ou plusieurs abstraites qui sont les seuls points d'accès pour créer des instances. Toutefois je vous conseille de laisser malgré tout les Factory "locales" dans chaque classe. Cela évite d'éparpiller le code et si un jour une classe enfant est modifiée au point que son initialisation le soit aussi, il n'y aura pas à penser à faire des vérifications dans le code de la Factory séparée. De fait une Factory centrale ne peut être vue que comme un moyen de regrouper les Factories locales, sans pour autant se passer de ces dernières ou en modifier le rôle.

Conclusion

Peut-on aller encore plus loin ? Peut-on procéder d'une autre façon pour satisfaire toutes les exigences de la situation ? Je ne doute pas qu'une autre voie puisse exister, pourquoi pas plus élégante. Encore faut-il la découvrir. C'est comme en montagne, une fois qu'une voie a été découverte pour atteindre le sommet plus facilement ça semble une évidence, mais combien ont du renoncer au sommet avant que le découvreur de la fameuse voie ne trace le chemin ?

Saurez-vous être ce premier de cordée génial et découvrir une voie alternative à la solution de la Factory ? Si tel est le cas, n'oubliez pas de laisser un commentaire !

Dans tous les cas : Stay Tuned !

Et pour jouer avec le code, le projet de test VS 2008 : VirtualInCtor.zip (5,99 kb)

C# - Les optimisations du compilateur dans le cas du tail-calling

Les optimisations du compilateurs C# ne sont pas un sujet de discussion très courant, en tout cas on voit très nettement que le fait d'avoir quitté l'environnement Win32 pour l'environnement managé de .NET a fait changer certaines obsessions des codeurs... Ce n'est pas pour autant que le sujet a moins d'intérêt ! Nous allons voir cela au travers d'une optimisation particulière appelée "tail-calling" (appel de queue, mais je n'ai pas trouvé de traduction française, si quelqu'un la connaît, qu'il laisse un commentaire au billet).

Principe

On appelle "tail-calling" un mécanisme d'optimisation du compilateur qui permet d'économiser les instructions exécutées en même temps que des accès à la pile. Les circonstances dans lesquelles le compilateur peut utiliser cette optimisation sont celles où une méthode se termine par l'appel d'une autre, d'où le nom de tail-calling (appel de queue).

Prenons l'exemple suivant :

static public void Main()
{
    Go();
}
 
static public void Go()
{
    Première();
    Seconde();
    Troisième();
}
 
static public void Troisième()
{
}

Dans cet exemple le compilateur peut transformer l'appel de Troisième() en un appel de queue (tail-calling). Pour mieux comprendre regardons l'état de la pile au moment de l'exécution de Seconde()  : Seconde()-Go()-Main()

Quand Troisième() est exécutée il devient possible, au lieu d'allouer un nouvel emplacement sur la pile pour cette méthode, de simplement remplacer l'entrée de Go() par Troisième(). La pile ressemble alors à Troisième()-Main().

Quand Troisième() se terminera elle passera l'exécution à Main() au lieu de transférer le trait à Seconde() qui immédiatement devra le transférer à Main().

C'est une optimisation assez simple qui, cumulée tout au long d'une application, et ajoutée aux autres optimisations, permet de rendre le code exécutable plus efficace.

Quand l'optimisation est-elle effectuée ?

La question est alors de savoir quand le compilateur décide d'appliquer l'optimisation de tail-calling. Mais dans un premier temps il faut se demander de quel compilateur nous parlons.... Il y existe en effet deux compilateurs dans .NET, le premier prend le code source pour le compiler en IL alors que le second, le JIT, utilisera ce code IL pour créer le code natif. La compilation en IL peut éventuellement placer certains indices qui permettront de mieux repérer les cas où le tail-calling est applicable mais c'est bien entendu dans le JIT que cette optimisation s'effectue.

Il existe de nombreuses règles permettant au JIT de décider s'il peut ou non effectuer l'optimisation. Voici un exemple de règles qui font qu'il n'est pas possible d'utiliser le tail-calling (par force cette liste peut varier d'une implémentation à l'autre du JIT) :

  • L'appelant ne retourne pas directement après l'appel;
  • Il y a une incompatibilité des arguments passés sur la pile entre l'appelant et l'appelé ce qui imposerait une modification des arguments pour appliquer le tail-calling;
  • L'appelant et l'appelé n'ont pas le même type de retour (données de type différents, void);
  • L'appel est est transformé en inline, l'inlining étant plus efficace que le tail-calling et ouvrant la voie à d'autres optimisations;
  • La sécurité interdit ponctuellement d'utiliser l'optimisation;
  • Le compilateur, le profiler, la configuration ont coupé les optimisations du JIT.

Pour voir la liste complète des règles, jetez un oeil à ce post.

Intérêt de connaitre cette optimisation ?

Normalement les optimisations du JIT ne sont pas un sujet intéressant au premier chef le développeur. D'abord parce qu'un environnement managé comme .NET fait qu'à la limite ce sont les optimisations du code IL qui regarde directement le développeur et beaucoup moins la compilation native qui peut varier d'une plateforme à l'autre pour une même application. Ensuite il n'est pas forcément judicieux de se reposer sur les optimisations du JIT puisque, justement, ce dernier peut être différent sans que l'application ne le sache.

Qui s'intéresse à l'optimisation du tail-calling alors ? Si vous écrivez un profiler c'est une information intéressante, mais on n'écrit pas un tel outil tous les jours... Mais l'information est intéressante lorsque vous déboguez une application car vous pouvez vous trouver face à une pile d'appel qui vous semble "bizarre" ou "défaillante" car il lui manque l'une des méthodes appelées !

Et c'est là que savoir qu'il ne faut pas chercher dans cette direction pour trouver le bug peut vous faire gagner beaucoup de temps... Savoir reconnaître l'optimisation de tail-calling évite ainsi de s'arracher les cheveux dans une session de debug un peu compliquée si on tombe en plus sur un pile d'appel optimisée. Un bon debug consiste à ne pas chercher là où ça ne sert à rien (ou à chercher là où c'est utile, mais c'est parfois plus difficile à déterminer !), alors rappelez-vous du tail-calling !

Et stay tuned !

nota: le présent billet est basé sur un post de l'excellent blog ASP.NET Debugging