Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Silverlight et le multi-thread (avec ou sans MVVM)

La “parallélisation” des traitements est devenu un enjeu majeur et paradoxal du développement et Silverlight n’échappe pas à ce nouveau paradigme.

Nouveau Paradigme ?

Nouveau paradigme, cela fait toujours un peu “attention je suis en train de dire quelque chose d’intelligent et j'utilise une tournure à la mode qui fait savante pour le faire remarquer”… Mais parfois cela a aussi un vrai sens et une vraie justification !

Nouveau paradigme donc que la programmation multitâche, ou pour être encore plus chébran, parlons de programmation parallèle, voire de parallélisme tout en évitant le trop prétentieux “massivement parallèle” (nos PC ne comptent pas encore la centaine de “cores” – plutôt que “cœurs” pour faire totalement geek – qu’Intel prévoit pour dans quelques temps).

On le sait depuis un moment, la fréquence des processeurs n’évoluera plus comme elle l’a fait jusqu’à lors. Le fondeur de processeurs se retrouve ainsi face à un mur infranchissable, un peu comme l’était le mur du son pour l’aviation jusqu’à ce jour d’octobre 47 ou le pilote Yeager à bord du Bell-X-1 finit par traverser les briques de ce mur qu’on avait cru impénétrable. Pour nous il s’agit du “mur des performances”. On miniaturise, on diminue l’épaisseur des couches, on tasse les transistors, mais on butte sur la fréquence. Le seul moyen d’aller plus vite reste de multiplier les processeurs… Et comme multiplier les cartes mères atteint vite une limite en termes de prix et de consommation électrique, on essaye désormais de tasser des cœurs sur les puces comme on tassait les transistors à l’étape précédente. Ce n’est pas une option et il n’y a pas de “plan B”, c’est la seule solution jusqu’à ce qu’existent les ordinateurs quantiques (dont la faisabilité reste totalement à prouver).

Deux, quatre, huit, trente-deux, bientôt cent cœurs dans les machines de monsieur Tout-Le-Monde. 

Hélas ce n’est pas avec les techniques de programmation utilisées jusqu’aujourd’hui que les informaticiens vont s’en sortir. Les fondeurs ont trouvé le moyen de percer le “mur des performances”, et si les informaticiens ont une idée de comment ils vont relever le défi, ils ne se sont pas encore dotés des outils permettant de le faire : une formation ad hoc et une approche radicalement différente de la programmation.

On peut ainsi réellement, et sans emphase exagérée, parler de “nouveau paradigme” de la programmation parallèle car seule cette nouvelle approche va permettre aux logiciels de bénéficier des accélérations offertes par les processeurs modernes et encore plus par ceux à venir. Et pour cela il va falloir que les développeurs se mettent à niveau alors qu’ils sont déjà à la traîne de cette révolution…

J’ai dernièrement acquis une machine à 4 cœurs hyperthreadés, donc à 8 cœurs apparents. Avec certaines applications elle va à peine aussi vite que mon double cœurs E6700 overclocké (et pas toujours)… Je regarde la jolie fenêtre des performances de Windows, je vois ces huit magnifiques graphiques qui sont autant de preuves formelles de la puissance de la bête (et un enchantement de geek), mais un seul s’active vraiment, un autre le faisant mollement, et les 6 autres piquant un roupillon désespérant ! Ces applications je les maudis et dans la foulée leurs concepteurs incompétents qui ont programmé comme on le faisait il y a 20 ans : en pensant qu’un seul processeur anime la machine et que Windows va se débrouiller pour partager le temps (et que si l’utilisateur trouve que “ça rame” il n’a qu’à acheter une machine récente…).

En revanche je suis plein de compliments confraternels à l’égard des développeurs qui ont programmé les quelques applications qui savent se servir de mes huit cœurs ! Ces programmes qui s’essoufflaient sur le E6700 deviennent des bombes sur le i870, ou, au minimum de mes attentes, vont nettement plus vite, ce qui n’est déjà pas si mal.

Faire partie de ceux qu’on maudit ou de ceux qu’on félicite, il va falloir choisir son camp !

Programmer “parallèle” devient ainsi une obligation de fait, techniquement parlant. Mais il ne faut pas ignorer non plus les cas plus classiques où le parallélisme est le seul moyen de programmer certaines fonctions. C’est ce que nous verrons dans un instant au travers d’un exemple de code.

Mais paradoxal !

En effet, ce nouveau paradigme n’en est pas moins paradoxal . La raison est simple : d’un côté les multi-cœurs se généralisent dans les PC et de l’autre l’informatique mobile explose avec des téléphones aux interfaces graphiques à faire pâlir les progiciels PC les plus chers, des iPad, des EEEPC, et autres merveilleuses petites machines ayant toutes en commun d’être dotées d’un seul processeur rikiki et monocœur !

Alors que .NET 4.0 arrive avec des dizaines de méga de couches plus sophistiquées les unes que les autres, simultanément Silverlight et son mini Framework sont annoncés (avec XNA) comme l’interface des Phones 7, voire bientôt de certaines tablettes PC…

Faire le grand écart entre ces deux tendances extrêmes et néanmoins concomitantes relève du tour de force et place l’informaticien dans une situation paradoxale. Et pourtant ce sont deux défis que le développeur doit dès aujourd’hui relever, et simultanément ! Même en s’y mettant tout de suite, il part en retard (les “utilisateurs de base” possèdent depuis plusieurs années des machines à double cœurs !). Il faut donc programmer en visant la “scalability” ou “capacité à s’adapter”, s’adapter à la charge, s’adapter au matériel. Tirer le meilleur d’un Phone 7, faire cracher ses chevaux à un PC de bureau 8 cœurs. En gros il faut maîtriser la programmation parallèle.

En pratique on fait comment ?

On sort ses neurones du mode économie d’énergie certifié Energy Star et on se sert de l’ordinateur le plus massivement parallèle de l’univers : son cerveau :-)

Cela signifie adopter une nouvelle “attitude” face à la programmation quotidienne. La moindre classe doit être pensée dans un contexte multi-tâche. Cela doit devenir un automatisme, comme de finir chaque instruction par un point-virgule en C#.

Mais comme il n’est pas question dans ce blog de faire un cours complet sur le multi-tâche, attardons-nous sur un exemple concret et voyons comment introduire un peu de ces réflexes qui tendent vers une meilleur utilisation des ressources physiques sous-jacentes offertes par la machine hôte de nos chefs-d’œuvres compilés….

Un exemple concret

programmer en vue d’une utilisation multi-tâche n’est pas forcément simple. On peut être ainsi tenter d’y échapper jusqu’au jours où on tombe sur un cas tout simple qui va déboucher sur un magnifique plantage.

Comme le titre du billet l’indique, cela n’a rien de spécifique à MVVM, même si l’exemple sera programmé dans un mode de ce type (mais très édulcoré, ce n’est donc pas un exemple MVVM, pour cela se reporter à l’article complet que j’ai écrit sur le sujet).

Imaginons ainsi une vue présentant des données issues d’un ViewModel ou directement d’un Modèle (ce que MVVM autorise). Supposons ce dernier cas pour simplifier la “plomberie” de l’exemple. Ce Modèle simule une source de données un peu particulière puisqu’elle fonctionne en permanence et dépend de données externes qui arrivent un peu quand elles veulent. Cela n’a rien d’exotique, prenez un simple Chat, il y aura bien un Modèle fournissant les messages de l’interlocuteur fonctionnant en permanence et la fréquence d’arrivée des données (les messages) est aléatoire (quand l’interlocuteur a envi d’écrire).

Le modèle

Le Modèle de notre exemple sera bien plus simple encore : il fabrique des nombres aléatoires. Mais il les fabrique dans un thread séparé qui s’endort pour une durée aléatoire après chaque génération de nombre. Pour ce faire la classe RandomNumberPusher utilise un BackgroundWorker, classe du Framework permettant assez simplement de créer des tâches de fond (code source du projet à télécharger plus bas).

la Vue

Créons une vue (la MainPage sera suffisante pour cet exemple minimaliste) et attachons une instance de la classe RandomNumberPusher au DataContext de la grille principale. Ajoutons un TextBlock bindé (voir mon article sur le Binding Xaml) à la propriété Number. Et exécutons…

Le plantage…

A intervalle irrégulier l’instance du Modèle va générer en tâche de fond un nombre aléatoire et va déclencher la notification de changement d’état de la propriété Number (événement PropertyChanged). La Vue et son TextBlock étant bindés à cette dernière, la mise à jour du texte va se faire automatiquement. Oui mais… cela va planter. Pour une raison simple : seule le thread principal ayant créé les objets d’interface peut modifier ces derniers, or le générateur aléatoire fonctionne depuis un thread différent. On peut mettre le plantage encore plus en évidence si on tentait de modifier directement la propriété Text du TextBlock depuis la tâche de fond. Le modèle de programmation MVVM nous oblige à penser le code autrement (et c’est une bonne chose) et le plantage ne se verra pas forcément (le TextBlock ne changera pas de valeur). Mais à un moment donné il risque de s’afficher ceci :

Accès inter-thread non valide. Le couperet tombe.

Pour obtenir ce message de façon sûre il faut placer un point d’arrêt dans le thread de fond, s’arrêter dessus et repartir car le plus souvent le texte n’est tout simplement pas mis à jour. Comme je le disais plus haut, le plantage serait systématique si la tâche de fond mettait à jour directement le TextBlock ce que le montage de type MVVM évite. Le bug n’en est que plus sournois car il ne se passe rien, et rien, ce n’est pas grand chose ! Sur un écran un peu chargé il sera difficile de détecter le problème durant les tests.

Le mécanisme est simple :

Le thread de fond modifie la propriété Number, ce qui déclenche l’événement de changement de propriété (PropertyChanged), l’objet de binding est alors activé et tente de mettre à jour la valeur de la propriété Text du TextBlock. La plantage résulte du fait que la modification de ce dernier est effectué au travers d’un thread différent de celui gérant l’UI.

Le code xaml

   1:  <UserControl x:Class="SLMultiThread.MainPage"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
   5:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   6:      xmlns:model="clr-namespace:SLMultiThread.Models"
   7:      mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
   8:      <UserControl.Resources>
   9:          <model:RandomNumberPusher x:Key="random" />
  10:      </UserControl.Resources>
  11:      <Grid x:Name="LayoutRoot" DataContext="{StaticResource random}">
  12:          <TextBlock Text="{Binding Number, Mode=OneWay}"/>
  13:      </Grid>
  14:  </UserControl>

On note : la déclaration du namespace de notre Modèle, la déclaration d’une instance du Modèle en ressource, la définition du DataContext du LayoutRoot depuis cette dernière et enfin, le TextBlock bindé à la propriété Number.

Le code C# de la classe RandomNumberPusher n’est pas en cause dans le mécanisme du plantage. Elle est programmée en répondant aux exigences d’une classe dite Thread-Safe. Donc pour l’instant oublions là.

Les solutions

Plusieurs aspects sont à prendre en compte lors d’une programmation multi-thread. Le plantage que nous venons de mettre en évidence n’est qu’un aspect des choses, limité uniquement à la spécificité de Xaml qui ne prend pas en charge la mise à jour de l’UI via un thread autre que le principal. On trouvait déjà ce problème sous Windows Forms d’ailleurs.

Concernant ce problème spécifique il y a plusieurs approches, mais au final il n’y a qu’un moyen : faire en sorte que la manipulation de l’UI ne passe que par son thread propre. Ponctuellement on peut utiliser, comme sous Windows Forms, des mécanismes d’invocation transférant le contrôle au thread de l’UI. Mais ce que nous cherchons est une solution globale et “industrialisable”. La plupart des auteurs qui a abordé le sujet arrive à la même conclusion, il faut passer par un mécanisme centralisé de dispatch. Cette solution peut se mettre en œuvre de différentes façons, voyons directement celle qui m’apparait la plus simple à appliquer.

Le SmartDispatcher et la classe de base

Le principe peut se schématiser comme suit :

Au début les choses sont, par force, identiques : la tâche de fond modifie la propriété (Number dans notre exemple) et déclenche PropertyChanged dans le Modèle. Mais au lieu que cet événement soit traité directement par le binding, nous intercalons le Dispatcher, c’est lui qui va vérifier que l’appel a lieu depuis le thread d’UI. Dans l’affirmative le traitement se poursuivra normalement, dans la négative, ce qui est le cas de notre exemple, il transfèrera la notification de changement de propriété au binding via une invocation appropriée. Comme le Dispatcher se trouve dans le thread de l’UI, et qu’il va faire en sorte de rendre “sien” les appels qui proviennent d’autres threads, le binding ne verra que des appels intra-thread UI et non inter-threads.

Cette solution fonctionne en deux étapes. Il y a le Dispatcher, mais il y a aussi le routage des notifications vers ce dispatcher. Modifier dans chaque classe l’appel à PropertyChanged pour qu’il pointe vers le Dispatcher serait fastidieux et source d’erreur. Le plus simple est donc de créer une classe de base dont hériterons les Modèles ou les ViewModels plus généralement (la connexion directe d’une Vue à un Modèle utilisée dans l’exemple est certes autorisé par MVVM mais ce n’est pas le “montage” le plus courant, le ViewModel étant souvent nécessaire pour la prise en charge des commandes, l’adaptation des données et la persistance de la Vue).

Le mécanisme du Binding et des patterns comme MVVM reposent sur un présupposé : que les classes reliées par binding à l’UI prennent en charge INotifyPropertyChanged. Il est donc raisonnable de mettre en œuvre une classe de base implémentant cette interface et s’occupant automatiquement de la liaison avec le Dispatcher.

Le seul problème que peut poser cette approche est que le ViewModel soit déjà dans l’obligation d’hériter d’une classe de base. Cela peut être le cas si vous utilisez un framework MVVM ou autre. C# ne gérant pas l’héritage multiple (bien heureusement), il faudra alors fusionner la solution du Dispatcher à la classe de base déjà utilisée. A moins que celle-ci ne prenne déjà en charge un mécanisme équivalent, mais dans ce cas je peux aller me coucher vous n’avez plus besoin de mes conseils ! :-)

Le code du Dispatcher

Parmi les implémentations existantes j’ai une préférence pour celle de Jeff Wilcox qui est un Senior Software Development Engineer chez Microsoft dans l’équipe de Silverlight… Autant se servir aux bonnes sources ! Son implémentation est sous licence Ms-PL (licence libre publique spécifique à Microsoft). J’avais dans mes tablettes un code assez proche mais pas aussi complet et on peut trouver des choses assez équivalentes dans le portage de Cairngorm sur Silverlight (Cairngorm sur CodePlex) , un framework MVC traduit depuis l’original de même nom conçu pour Flex de Adobe (un produit proche de Silverlight pour ceux qui ne connaissent pas).

   1:  using System;
   2:  using System.Windows;
   3:  using System.ComponentModel;
   4:  using System.Windows.Threading;
   5:   
   6:  namespace SLMultiThread.ThreadTools
   7:  {
   8:      // (c) Copyright Microsoft Corporation.
   9:      // This source is subject to the Microsoft Public License (Ms-PL).
  10:      // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  11:      // All other rights reserved.
  12:      
  13:      
  14:          /// <summary>
  15:          /// A smart dispatcher system for routing actions to the user interface
  16:          /// thread.
  17:          /// </summary>
  18:          public static class SmartDispatcher
  19:          {
  20:              /// <summary>
  21:              /// A single Dispatcher instance to marshall actions to the user
  22:              /// interface thread.
  23:              /// </summary>
  24:              private static Dispatcher instance;
  25:   
  26:              /// <summary>
  27:              /// Backing field for a value indicating whether this is a design-time
  28:              /// environment.
  29:              /// </summary>
  30:              private static bool? designer;
  31:   
  32:              /// <summary>
  33:              /// Requires an instance and attempts to find a Dispatcher if one has
  34:              /// not yet been set.
  35:              /// </summary>
  36:              private static void requireInstance()
  37:              {
  38:                  if (designer == null)
  39:                  {
  40:                      designer = DesignerProperties.IsInDesignTool;
  41:                  }
  42:   
  43:                  // Design-time is more of a no-op, won't be able to resolve the
  44:                  // dispatcher if it isn't already set in these situations.
  45:                  if (designer == true)
  46:                  {
  47:                      return;
  48:                  }
  49:   
  50:                  // Attempt to use the RootVisual of the plugin to retrieve a
  51:                  // dispatcher instance. This call will only succeed if the current
  52:                  // thread is the UI thread.
  53:                  try
  54:                  {
  55:                      instance = Application.Current.RootVisual.Dispatcher;
  56:                  }
  57:                  catch (Exception e)
  58:                  {
  59:                      throw new InvalidOperationException("The first time SmartDispatcher is used must be from a user interface thread. Consider having the application call Initialize, with or without an instance.", e);
  60:                  }
  61:   
  62:                  if (instance == null)
  63:                  {
  64:                      throw new InvalidOperationException("Unable to find a suitable Dispatcher instance.");
  65:                  }
  66:              }
  67:   
  68:              /// <summary>
  69:              /// Initializes the <see cref="SmartDispatcher"/> system, attempting to use the
  70:              /// RootVisual of the plugin to retrieve a Dispatcher instance.
  71:              /// </summary>
  72:              public static void Initialize()
  73:              {
  74:                  if (instance == null)
  75:                  {
  76:                      requireInstance();
  77:                  }
  78:              }
  79:   
  80:              /// <summary>
  81:              /// Initializes the <see cref="SmartDispatcher"/> system with the dispatcher
  82:              /// instance.
  83:              /// </summary>
  84:              /// <param name="dispatcher">The dispatcher instance.</param>
  85:              public static void Initialize(Dispatcher dispatcher)
  86:              {
  87:                  if (dispatcher == null)
  88:                  {
  89:                      throw new ArgumentNullException("dispatcher");
  90:                  }
  91:   
  92:                  instance = dispatcher;
  93:   
  94:                  if (designer == null)
  95:                  {
  96:                      designer = DesignerProperties.IsInDesignTool;
  97:                  }
  98:              }
  99:   
 100:              /// <summary>
 101:              ///
 102:              /// </summary>
 103:              /// <returns></returns>
 104:              public static bool CheckAccess()
 105:              {
 106:                  if (instance == null)
 107:                  {
 108:                      requireInstance();
 109:                  }
 110:   
 111:                  return instance.CheckAccess();
 112:              }
 113:   
 114:              /// <summary>
 115:              /// Executes the specified delegate asynchronously on the user interface
 116:              /// thread. If the current thread is the user interface thread, the
 117:              /// dispatcher if not used and the operation happens immediately.
 118:              /// </summary>
 119:              /// <param name="a">A delegate to a method that takes no arguments and
 120:              /// does not return a value, which is either pushed onto the Dispatcher
 121:              /// event queue or immediately run, depending on the current thread.</param>
 122:              public static void BeginInvoke(Action a)
 123:              {
 124:                  if (instance == null)
 125:                  {
 126:                      requireInstance();
 127:                  }
 128:   
 129:                  // If the current thread is the user interface thread, skip the
 130:                  // dispatcher and directly invoke the Action.
 131:                  if (instance.CheckAccess() || designer == true)
 132:                  {
 133:                      a();
 134:                  }
 135:                  else
 136:                  {
 137:                      instance.BeginInvoke(a);
 138:                  }
 139:              }
 140:          }
 141:  }

Le SmartDispatcher est une enveloppe intelligente autour de la classe Dispatcher du Framework de Silverlight. Cette dernière a été conçue justement pour gérer le code de mise à jour de l’UI depuis d’autres threads. SmartDispatcher “habille” le Dispatcher de base en s’assurant qu’une instance est disponible. En fait, appeler le Dispatcher de SL serait suffisant, il est conçu pour cela, mais cela n’est pas possible depuis une tâche de fond, il faut donc que l’instance existe déjà. Le SmartDispatcher permet de s’assurer de cette condition liminaire et fournit un point d’accès connu et centralisé à l’instance en question.

Bien que SmartDispatcher s’occupe d’obtenir l’instance du Dispatcher automatiquement il est préférable de l’initialiser très tôt dans le code de l’application, pour s’assurer que le SmartDispatcher est bien opérationnel et disponible avant tout événement impliquant l’interface. C’est pourquoi il est préférable d’ajouter dans App.xaml le code suivant dans le constructeur de l’objet App, juste avant l’appel à InitializeComponent() :

SmartDispatcher.Initialize(Deployment.Current.Dispatcher); 

Le SmartDispatcher est initialisé avec le Dispatcher fourni par le namespace Deployment qui s’occupe majoritairement du manifest et d’informations sur l’application en cours.

La classe de base

Il y a plusieurs façons de l’implémenter, mais puisque j’ai fait le choix de vous montrer l’implémentation de SmartDispatcher, autant utiliser la classe de base qui a été écrite par Wilcox pour fonctionner avec ce dernier, ça semble logique…

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel;
   4:   
   5:  namespace SLMultiThread.ThreadTools
   6:  {
   7:      // (c) Copyright Microsoft Corporation.
   8:      // This source is subject to the Microsoft Public License (Ms-PL).
   9:      // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  10:      // All other rights reserved.
  11:   
  12:          /// <summary>
  13:          /// A base class for data objects that implement the property changed
  14:          /// interface, offering data binding and change notifications.
  15:          /// </summary>
  16:          public class PropertyChangedBase : INotifyPropertyChanged
  17:          {
  18:              /// <summary>
  19:              /// A static set of argument instances, one per property name.
  20:              /// </summary>
  21:              private static readonly Dictionary<string, PropertyChangedEventArgs> argumentInstances = 
  22:                  new Dictionary<string, PropertyChangedEventArgs>();
  23:   
  24:              /// <summary>
  25:              /// The property changed event.
  26:              /// </summary>
  27:              public event PropertyChangedEventHandler PropertyChanged;
  28:   
  29:              /// <summary>
  30:              /// Notify any listeners that the property value has changed.
  31:              /// </summary>
  32:              /// <param name="propertyName">The property name.</param>
  33:              protected void NotifyPropertyChanged(string propertyName)
  34:              {
  35:                  if (string.IsNullOrEmpty(propertyName))
  36:                  {
  37:                      throw new ArgumentException("PropertyName cannot be empty or null.");
  38:                  }
  39:   
  40:                  var handler = PropertyChanged;
  41:                  if (handler == null) return;
  42:                  PropertyChangedEventArgs args;
  43:                  if (!argumentInstances.TryGetValue(propertyName, out args))
  44:                  {
  45:                      args = new PropertyChangedEventArgs(propertyName);
  46:                      argumentInstances[propertyName] = args;
  47:                  }
  48:   
  49:                  // Fire the change event. The smart dispatcher will directly
  50:                  // invoke the handler if this change happened on the UI thread,
  51:                  // otherwise it is sent to the proper dispatcher.
  52:                  SmartDispatcher.BeginInvoke(() => handler(this, args));
  53:              }
  54:          }
  55:   
  56:  }

Cette classe implémente INotifyPropertyChanged qui est à la base du mécanisme de propagation “automatique” des changements de valeurs utilisé par le binding et sur lequel repose des patterns comme MVVM.

La classe utilise un astuce pour améliorer les performances : elle créée un dictionnaire des PropertyChangedEventArgs de telle façon à ce que ce soit toujours la même instance d’arguments qui soit utilisée pour une propriété donnée. Ainsi, et surtout lorsque les valeurs changent très souvent, le Garbage Collector n’est pas submergé de petits objets à durée de vie ultra-courte. Il est certes optimisé pour cela, mais moins il y a de créations / destructions moins le GC a de travail et tout va plus vite.

Une autre optimisation est importante, elle concerne la détection de la nécessité ou non de transiter via le Dispatcher. Dans le cas où cela n’est pas nécessaire le message de notification suit son cours. Il est ainsi traité immédiatement alors que le passage via le Dispatcher créé un délai (même infime).

Le code exemple

Le projet complet (VS2008 SL3) est téléchargeable ici: SLMultiThread.zip (9,55 kb)

Les autres facettes du multi-thread

Ici nous n’avons fait qu’effleurer le sujet en abordant un problème très spécifique du multithreading sous Silverlight. Si vous regardez le code de plus près, notamment la classe RandomNumberPusher que nous n’avons pas étudiée dans ce billet, vous y trouverez des techniques simples propres au mutithreading comme l’utilisation d’un objet “locker” pour assurer le verrouillage des lectures et écritures entre les threads.

Le sujet est vaste et il serait possible d’en parler des heures. Je réserve cela pour un éventuel futur article, il faut bien en laisser un peu pour plus tard… Une raison de plus de …

Stay Tuned !

Article ! Le Binding Xaml, sa syntaxe, éviter ses pièges… (WPF/Silverlight)

imagePromis dans mon dernier billet, le voici enfin ! Ce nouvel article dédié au binding Xaml pèse 77 pages (le record était tenu jusqu’à lors par mon article M-V-VM avec Silverlight avec 70 pages) et se présente sous la forme d’un PDF et de 9 projets exemples.

Le binding cet inconnu … J’en parlais dans mon billet Le retour du spaghetti vengeur, le binding avec sa syntaxe pleine d’accolades et de chaînes de caractères non contrôlées à la compilation est un piège à bugs et la porte ouverte au code spaghetti.

Plutôt que de me lamenter et vous laisser vous embourber dans la sauce de ces spaghetti là, j’ai pris ma plume, et voici le résultat : 77 pages sur le binding Xaml, son fonctionnement, ses différentes syntaxes le tout illustré par des exemples clairs. L’article aborde aussi le débogue du binding assez délicat puisque Xaml échappe au débogueur de Visual Studio, et que le binding n’est que partiellement couvert par Intellisense.

Des conseils, du vécu, beaucoup d’exemples, voilà ce que vous trouverez dans cet article à télécharger en suivant le lien suivant :

Le Binding Xaml – Maîtriser sa syntaxe et éviter ses pièges (WPF/Silverlight)

(Attention, avant de cliquer sur le bouton “télécharger” attendez que la fiche détail de l’article soit bien affichée, sinon le site pensera que vous tentez un accès à une ressource non publique et vous demandera un login… Je reçois tous les jours des demandes d’ouverture de compte provenant de lecteurs trop impatients qui cliquent trop vite sur le bouton ! La prochaine version du site en Silverlight est en préparation et corrigera ce petit problème, mais d’ici là : slow down cowboy !).

Pour vous aider à vous faire une idée du contenu de l’article voici son sommaire :

Sommaire

  • Références    5
  • Code Source    6
  • Préambule    7
  • Le Binding Xaml : Ange ou Démon ?    7
    • Le Binding    8
    • Définition    8
    • Utilisations    9
    • Schéma du principe    9
    • Déclaration    10
      • Par code    10
      • En Xaml    11
  • Les modes de binding    13
    • Le mode OneTime    13
    • Le mode OneWay    13
    • Le mode TwoWay    14
      • Gestion du timing    14
    • Le mode Default    16
    • Le mode OneWayToSource    16
  • Hiérarchie de valeur    18
    • Règles de précédences    19
  • La notion de  DataContext    20
  • Les convertisseurs de valeur    21
    • Définition    21
    • Scénario    21
    • Implémentation    22
    • Utilisation    23
      • Instanciation    23
      • Invocation    23
      • Bonnes pratiques    24
      • Pour résumer    26
  • Les dangers du Binding    27
    • Des chaînes non contrôlées    27
    • Un langage dans le langage    28
  • On fait quoi ?    28
  • Déboguer le Binding    28
    • Vigilance    28
    • Une code Xaml court    29
    • Refactoring sous contrôle    29
    • Utiliser des outils intelligents    29
    • Utiliser Expression Blend    29
    • Vérifier les erreurs de binding dans la fenêtre de sortie    30
    • Créer un fichier des erreurs de Binding    30
    • La feinte du convertisseur inutile    32
    • Quelques outils supplémentaires    33
      • Spy++    33
      • ManagedSpy    33
      • Snoop    34
      • Mole    34
      • Reflector    34
      • Vos neurones    34
  • Les syntaxes du Binding    34
    • Le binding simple    34
      • Binding direct    35
      • Binding sur une propriété    35
      • Binding sur une sous-propriété du contexte    36
      • L’Element Binding    37
      • Convertisseurs de valeur    37
        • Paramètres de conversion    38
      • StringFormat    40
        • Injection de culture    41
        • Le ContentStringFormat    42
        • Gérer les nuls    43
    • Le Binding Multiple    44
      • La classe Personne    44
      • Le code du multi convertisseur    45
      • Le code Xaml    46
    • Le Binding XML    47
      • Binding sur une source Web    47
      • Binding sur une source XML en ressource    48
      • Binding sur une requête Linq To XML    49
    • Le Binding Relatif    52
      • Binding to Self    52
      • Le TemplatedParent Binding    53
      • Le Binding sur recherche d’ancêtre    54
      • Le Binding PreviousData    56
        • La source de données    58
        • La visibilité des flèches : la magie de PreviousData et du Binding Multiple    58
        • Le contenu des textes    61
      • Le Template binding    61
        • Utilité    61
      • Le Collection Binding    67
      • Le Priority Binding    69
  • Les propriétés de tous les bindings    74
  • Conclusion    76

Avril est froid et pluvieux mais les soirées rallongent, c’est le printemps même si cela n’y ressemble pas encore beaucoup, grâce à moi vous savez maintenant comment occuper vos soirées et vos weekend au lieu de faire l’idiot dans les embouteillages des vacances : Lisez cet article !

Et Stay Tuned !

Article: M-V-VM avec Silverlight

Model-View-ViewModel, je vous en parlais il y a très peu de temps (MVVM, Unity, Prism, Inversion of Control…) et je vous avais promis un exemple pour rendre tout cela plus concret. C’est fait ! Et même mieux, un article de 70 pages l’accompagne !

Vous saurez tout (ou presque) sur cette design pattern absolument incontournable pour développer sérieusement sous Silverlight.

Après des explications sur la pattern elle-même l’article vous présente une application exemple entièrement réalisée avec ce qu’il y a “out of the box”. J’ai fait le choix de n’utiliser aucun Framework existant (Prism, Cinch, Silverlight.FX, MVVM Light…) pour vous montrer que M-V-VM peut entièrement être mis en œuvre “à la main” sans aide extérieure.

Cela ne veut pas dire que tous ces Frameworks (dont l’article parle aussi) ne sont pas intéressants, au contraire ! Mais comment choisir une librairie facilitant M-V-VM si vous ne savez pas comment mettre en œuvre cette pattern et si vous ne connaissez pas les difficultés qu’elle soulève autant que ses avantages ?

Cet article vous permettra de faire le point sur M-V-VM et de pouvoir ensuite choisir le Framework qu’il vous plaira en toute connaissance de cause ou bien cela vous aidera à développer votre propre solution. Après tout, l’application exemple fonctionne parfaitement sans aucun de ces Frameworks….

Le code source du projet est fourni. En raison de l’énorme avantage de la gestion des commandes introduites dans Silverlight 4 (toujours en beta) l’article utilise cette version qui sera bientôt disponible. Tout est expliqué pour savoir comment faire fonctionner le code exemple à l’aide de VS 2010 ou Blend 4 (en beta aussi).

L’article peut être lu sans faire tourner le code si vous ne souhaitez pas installer la beta de SL4, et la première partie théorique s’applique aussi bien à M-V-VM sous SL3.

Bonne lecture !

(PS: n'oubliez pas que depuiis août 2012 les articles sont regroupés sur la page publications).

Téléchargement ici : M-V-VM avec Silverlight, de la théorie à la pratique.

 

 

 

 

MVVM, Unity, Prism, Inversion of Control…

Dans la série, presque culte, que je diffuse régulièrement et dont le titre serait “comment ne pas passer pour un idiot à la machine à café” la belle brochette de termes que je vous propose aujourd’hui a de quoi plonger le non initié dans une profonde perplexité !

Tous ces termes sont plus ou moins liés entre eux, les uns renvoyant aux autres ou les autres utilisant les uns.

MVVM ou plutôt M-V-VM

“Ah oui ! avec les tirets on comprend tout de suite mieux !” (Jean Sérien, lecteur assidu).

Model-View-ViewModel. Ca bégaye un peu mais c’est pourtant ça.

En réalité vous connaissez certainement le principe qui vient de M-V-C, Model-View-Controler, Modèle-Vue-Contrôleur. Une stratégie de développement visant à découpler le plus possible les données de leurs représentations. Sinon je vous laisse vous faire une idée, depuis dix ans au moins le Web est plein de pages présentant cette approche. Le billet du jour n’est pas d’aller dans les détails, juste de planter le décor avant d’aller plus loin dans d’autres billets.

Alors pourquoi MVVM plutôt que MVC ? C’est en tout cas le premier terme qui devient de plus en plus à la mode dans le monde WPF / Silverlight. Une réponse un peu courte je l’avoue… Mais même Prism (j’en parle plus loin) dans sa version 2 et qui, entre autres choses, présente cette stratégie ne l’évoque pas sous ce nom mais sous diverses autres formes. Autant dire que terminologiquement parlant, savoir ce que veut dire MVVM est un must de l’instant !

Donc M-V-VM est un modèle de développement, une façon de structurer l’écriture du code dont le but ultime est de séparer le Modèle (les données) des Vues (l’IHM ou l’UI), le tout en passant par un intermédiaire, le contrôleur qui s’appelle dans cette variante ViewModel (et pour lequel je ne trouve pas de traduction vraiment limpide).

La vraie question, légitime est : ce changement d’appellation cache-t-il un changement de rôle ? Les puristes vous diront bien entendu que oui, sinon ils n’auraient pas inventé un nouveau nom… Les pragmatiques dont je suis vous diront que lorsqu’on veut relancer un produit un peu trop connu mais toujours valable, on change son nom ce qui permet de communiquer à nouveau dessus plus facilement… Les méthodologistes sont des futés… Certains vont jusqu’à parler de parenté avec la pattern Presentation-Model, un peu comme d’autres vous expliquent que C# vient de C++ plutôt que de dire qu’il vient de Delphi. S’inventer des origines plus ou moins nobles (ou du moins qui le paraissent) est un jeu finalement très humain qui nous intéresse que peu ici.

Mais pour être totalement juste j’ajouterais qu’il y a en fait une véritable évolution de point de vue entre M-V-C, dont on parlait déjà du temps de Delphi Win32 et C++, et M-V-VM qu’on applique à WPF ou Silverlight. Par force le temps passe, les outils se sophistiquent et les pensées s’aiguisent. Donc, bien sûr, il y a quelques variations entre ce qu’on entendait par “contrôleur” dans MVC et ce qu’on entend par ViewModel dans MVVM. Mais, à mon sens, cela est mineur. D’ailleurs de nombreux forums proposent des threads à rallonge où des tas d’experts (autoproclamés) se déchirent sur ces nuances. Dépassons ces débats stériles.

Pourquoi MVVM est-il en train de s’imposer sous WPF / Silverlight ? Simplement parce que “out-of-the-box” Visual Studio et Blend ne permettent pas directement de vraiment séparer le travail entre les développeurs et les designers. Volonté pourtant clairement affichée de ces produits. Les outils eux-mêmes mélangent un peu des deux compétences (un peu plus de design dans Blend, un peu plus de développement dans VS).

Mais lorsqu’il faut écrire de vraies applications avec WPF ou Silverlight et qu’on a de vraies équipes à gérer, c’est à dire des informaticiens d’un côté et un ou plusieurs infographistes de l’autre, il faut bien convenir que sans une approche appropriée ça coince. Non pas que techniquement les outils (Blend, VS) ne sont pas à la hauteur, ni même que les langages (Xaml, C#, VB) ne sont pas bien étudiés. Au contraire. La faille est ici humaine et méthodologique.

En réalité tous les outils du monde, aussi bien faits soient-ils, ne peuvent permettre aux informaticiens l’économie d’une méthodologie bien pensée… Ce que Microsoft propose avec WPF / Silverlight, Xaml et le Framework .NET est très nouveau et il faut du temps pour maîtriser ces outils qui évoluent beaucoup en peu de temps. Dégager des stratégies de développement pertinentes est plus long que de créer un nouveau compilateur.

Or, si on regarde une fenêtre WPF ou une page Silverlight, on voit immédiatement qu’il existe du code-behind joint au fichier Xaml selon un modèle vieux de 15 ans (le couple de fichiers DFM/PAS des TForm de Delphi 1 en 1995 par exemple). La tentation est alors bien grande de coder l’action d’un click sur un bouton directement dans ce code puisqu’il est là pour ça ! Si c’est autorisé, c’est utilisé. Et malgré une séparation forte du code métier (ce qui suppose d’avoir fait cet effort) on se retrouve au final avec une interface qui contient du code qui en appelle d’autres (le code métier ou les données) voire pire, qui contient des petits bouts de traitement ! Autant dire que certains codes Silverlight ou WPF que j’ai audités récemment ressemblent comme deux gouttes d’eau au code spaghetti Delphi que j’ai audité dans les 15 dernières années… La vie n’est qu’un éternellement recommencement disent certains. Je n’ai pas cette vision nihiliste de l’existence, mais en informatique, hélas, cela se confirme !

Vade Retro, Satana !

En dehors de l’imbroglio qui résulte de cette situation, le code final de l’application est donc difficilement maintenable, intestable de façon cloisonnée (donc obligeant à des tests depuis l’interface par des testeurs ou des outils simulant un humain qui clique sur les boutons) et, comble du comble, il est en réalité presque impossible de séparer proprement le travail des informaticiens et des designers ! L’enfer brûlant s’ouvre alors sous les pieds des infortunés développeurs qui croyaient pourtant avoir “tout fait comme il faut”. Bad luck. On est loin du compte.

M-V-VM permet ainsi de régler plusieurs problèmes d’un coup : meilleure maintenabilité du code, vraie séparation entre code et interface, tests unitaires du code possibles sans même qu’il existe encore d’interface (ou totalement indépendamment de celle-ci), et enfin vraie séparation entre infographistes et informaticiens.

En pratique le fichier de code-behind ne sert plus à rien et reste vide. Les Vues (interface utilisateur) utilisent leur DataContext pour se lier à une instance du ViewModel qui lui sait utiliser le Model (les données). Grâce à cette notion de DataContext et au puissant Data Binding de Xaml on peut mettre en place une séparation bien nette avec, au final, peu de code en plus.

Reste le problème des événements de l’UI comme le simple clic sur un bouton. Comment le programmer sans code-behind ? Sous WPF la réponse est simple puisqu’il existe une gestion des commandes complète (le CommandManager et d’autres classes). L’interface ICommand est la base de ce mécanisme, les contrôles qui ont des propriétés de ce type peuvent donc être bindées au code des actions se trouvant alors placé dans le ViewModel, comme n’importe quelle autre propriété peut être liée via le binding.

Ce modèle est d’une grande élégance il faut l’avouer. J’aime plaisanter et je joke souvent dans mes billets, mais il ne faudrait pas croire que je passerais du temps à écrire un long billet sur un sujet futile ou une simple réinvention stérile de vieux procédés. M-V-VM est “la” façon de coder proprement sous WPF et Silverlight. Il n’y en a pas d’autres (d’aussi abouties pour l’instant en tout cas). Tout le reste ne peut que créer du code spaghetti. Cela peut choquer, vous paraître extrême dit comme ça, mais pourtant c’est la vérité…

C’est pourquoi je vous proposerais bientôt un autre billet abordant par l’exemple la stratégie M-V-VM.

Mais pour l’instant revenons deux secondes sur cette fameuse gestion des commandes de WPF. Et Silverlight ? Hélas malgré l’enthousiasme que vous me connaissez pour ce dernier et sa convergence avec WPF, il faut reconnaître que cette dernière n’est pas terminée. On le dit souvent, Silverlight est un sous ensemble de WPF, même si ce n’est plus tout à fait vrai. On avance aussi que le plugin est trop petit pour contenir tout ce qui fait WPF. C’est vrai. Mais comme je le disais dans un autre billet, avec une installation personnalisée du Framework ce dernier peut tenir dans 30 Mo. C’est bien plus gros que le plugin SL actuel. Mais j’ai joué pas plus tard qu’hier avec une application écrite en Adobe Air, et justement le download de Air fait environ 30 Mo. Pour un résultat tellement navrant que j’ai tout désintallé d’ailleurs. Alors d’ici quelques versions de SL, peut-être Microsoft (et les utilisateurs) accepteront-ils l’idée qu’un produit aussi bien ficelé puisse justifier les 10 ou 20 Mo de plus à télécharger (une fois) pour avoir enfin un équivalent absolu de WPF… Mais ce n’est pas encore le cas, force est de le constater.

Donc Silverlight pour le moment propose ICommand, l’interface, mais pas tout le reste qui fait la gestion des commandes sous WPF. De fait, dans Silverlight il faut ajouter un peu de code pour recréer cette dernière. C’est le but d’un petit framework comme “MVVM Light” qui se base sur les principes édictés dans Prism. Mais on peut inventer d’autres solutions.

Donc pour faire du M-V-VM avec WPF, tout est dans la boîte, avec Silverlight il faut ajouter une simulation, même minimaliste, de la gestion des commandes afin que ces dernières puissent être intégrées au ViewModel et que la Vue puisse être liée aux actions du ViewModel par un binding sur des propriétés de type ICommand.

Prism 2

Cela nous amène à évoquer Prism “patterns and practices : Composite WPF and Silverlight”.

Il s’agit d’une réflexion méthodologique dont les fruits se présentent sous la forme de conseils et d’applications exemples. Prism couvre de nombreux champs et propose des solutions élégantes.

Cela ne serait pas fairplay d’en parler en quelques lignes, c’est un travail d’une grande qualité qui ne se résume pas en trois phrases. Je vous conseille vivement sa lecture, rien ne peut remplacer cet investissement personnel.

Mais puisqu’aujourd’hui le but est de planter le décors, il aurait été tout aussi impardonnable de “zapper” Prism, surtout que certaines solutions permettant d’implémenter M-V-VM sous Silverlight y sont présentées.

Je reviendrai certainement un jour sur Prism, c’est d’une telle richesse que j’ai vraiment envie de vous en parler plus. Prism est incontournable, mais ne peut se résumer dans un billet. Si je trouve le moyen d’un tel tour de force alors je vous en reparlerai en détails. Mais le meilleur conseil c’est encore que vous le lisiez vous-mêmes…

Unity Application Block

Unity est un framework Microsoft qu’on trouve dans la série “patterns & practices”. Une mine d’or de la méthodologie sous .NET d’où provient aussi Prism dont je parlais ci-dessus.

Il faut noter que MS fait d’immenses efforts pour ouvrir la réflexion sur les méthodologies tout en tentant le tour de force de rendre ces cogitations tangibles. Lorsqu’on connaît un peu les méthodologistes pour les avoir fréquentés, toujours perdus dans leurs hautes sphères et rebutant à se “salir” les mains avec du code, on imagine à quel point l’effort de Microsoft est louable et tellement remarquable que cela mérite d’insister un peu sur la valeur de patterns & practices.

Ainsi, Unity, tout comme Prism, ne se contente pas d’être un gros pavé d’explications qui seraient réservées à une certaine élite. Unity se sont bien entendu des explications mais aussi du code qui permet d’ajouter l’action à la réflexion. Du bon code même. Des librairies utilisables pour simplifier le développement d’applications qui se conforment aux best practices. Je n’irais pas jusqu’à dire que les travaux présentés dans patterns & practices sont d’abord aisés, les cogitations méthodologiques réclament toujours un minimum d’attention, mais la volonté assumée de joindre la pratique et l’usage à la réflexion rend tout cela accessible à qui se donne un peu de temps pour lire et expérimenter.

Pour ce qui est de Unity, ce bloc se concentre sur la conception d’un conteneur “léger” et extensible permettant de faire de l’injection de dépendances (dependency injection).

De quoi s’agit-il ?

Comme indiqué en introduction de ce billet, tout ce dont je vous parle aujourd’hui tourne autour des mêmes besoins, de concepts proches ou qui se font écho les uns aux autres. L’injection de dépendances est une réponse à une série de problèmes qui se posent lorsqu’on désire séparer proprement des blocs de code tout en évitant de construire une “usine à gaz”.

Séparation de code, M-V-VM, Prism, ce sont autant de caméras placées autour d’une même scène. Des angles de vue différents, des manières d’entrer dans le sujet différentes, mais au final un but qui, s’il n’est pas commun, vise une même finalité. La conception d’applications souples, maintenables et extensibles.

Grâce à Unity vous disposez à la fois d’explications claires et d’une librairie permettant de gérer convenablement les injections de dépendances. Encore une terminologie “savante” pour des choses finalement pas si compliquées que cela. Mais pour en rajouter une couche disons que l’injection de dépendances n’est qu’une façon de gérer l’ “inversion de contrôle”.

Inversion of Control

“Pitié ! Arrêtez-le !” (Jean Peuplus, lecteur parisien)

On peut comprendre ce lecteur. Une telle avalanche ne risque-t-elle pas de tout emporter sur son passage, au risque de rendre ce billet contre-productif ?

La question mérite d’être posée, mais je fais le pari que vous tiendrez jusqu’au bout (qui est proche je vous rassure).

Le problème à résoudre

Pour info : j’utilise ci-après des parties de la documentation de Prism version octobre 2009

Vous avez une classe dont le fonctionnement dépend de services ou de composants dont les implémentations réelles sont spécifiées lors du design de l’application.

image

Les problèmes suivants se posent

  • Pour remplacer ou mettre à jour les dépendances vous devez faire des changements dans le code de la classe (classe A dans le schéma ci-dessus)
  • Les implémentations des services doivent exister et être disponibles à la compilation de la classe
  • La classe est difficile à tester de façon isolée car elle a des références “en dur” vers les services. Cela signifie que ces dépendances ne peuvent être remplacées par des mocks ou des stubs (maquette d’interface ou squelette non fonctionnel de code).
  • La classe contient du code répétitif pour créer, localiser et gérer ses dépendances.

Les conditions qui forcent à utiliser la pattern d’inversion de contrôle

  • Vous voulez découpler vos classes de leurs dépendances de telle façon à ce que ces dernières puissent être remplacées ou mises à jour avec le minimum de changement dans votre code, voire aucune modification.
  • Vous désirez construire vos classes de telle façon à ce qu’elles puissent dépendre d’implémentations inconnues ou indisponibles lors de la compilation.
  • Vous voulez pouvoir tester vos classes de façon isolée, sans faire usage des dépendances.
  • Vous voulez découpler la responsabilité de la localisation et de la gestion des dépendances de vos classes.

La solution

Il faut déléguer la fonction qui sélectionne le type de l’implémentation concrète des classes de services (les dépendances) à un composant ou une source externe à votre code.

Les implémentations

Il y a plusieurs façons d’implémenter la pattern d’inversion de contrôle. Prism en utilise deux qui sont l’Injection de dépendances (Dependency Injection) et le Localisateur de service (Service Locator).

image 

Injection de dépendances

L'injection de dépendances est un mécanisme qui permet d'implanter le principe de l'Inversion de contrôle. Il consiste à créer dynamiquement (injecter) les dépendances entre les différentes classes en s'appuyant généralement sur une description (fichier de configuration). Ainsi les dépendances entre composants logiciels ne sont plus exprimée dans le code de manière statique mais déterminées dynamiquement à l'exécution.

Le localisateur de services

Le localisateur de services est un mécanisme qui recense les dépendances (les services) et qui encapsule la logique permettant de les retrouver. Le localisateur de services n’instancie pas les services, il ne fait que les trouver, généralement à partir d’une clé (qui éviter les références aux classes réelles et permet ainsi de modifier les implémentations réelles). Le localisateur de services propose un procédé d’enregistrement qui liste les services disponibles ainsi qu’un service de recherche utilisé par les classes devant utiliser les dépendances et qui se chargent de les instancier.

Exemples

Même si les choses sont certainement plus claires maintenant (je l’espère en tout cas) il est certain que des exemples de code aideraient à visualiser tout cela. J’en ai bien conscience et j’ai prévu d’illustrer concrètement les principes expliqués dans ce billet. Toutefois en raison de la longueur de chaque exemple (nous parlons ici de techniques de découplement donc de nombreux fichiers et projets pour un seul exemple) il ne me semble pas raisonnable d’aller plus loin immédiatement. Laissons ce billet se terminer, et toutes ces notions tourner dans vos têtes avant de les appliquer !

Conclusion

Une terminologie ésotérique ne cache pas forcément des choses complexes. Le but de ce billet était de vous donner quelques clés pour mieux comprendre un ensemble de notions différentes mais participant toutes à une même finalité.

J’aborderai des exemples sous Silverlight très bientôt. Donc pour en savoir plus :

Stay Tuned !

Prism v2 (guidance and patterns for WPF and Silverlight)

Construire des applications modulaires offre de nombreux avantages : maintenabilité et évolutivité sont les premières qui viennent à l'esprit mais il en existe d'autres comme la meilleure séparation des tâches (travail parallèle d'équipes de développement sur des modules différents) par exemple.

Créer une architecture assurant la modularité d'une application n'est pas chose aisée. Bricoler "sa" solution dans "son" coin donne l'impression de gagner du temps (pas besoin d'apprendre un framework existant) mais montre souvent ses limites et ce, au pire moment, c'est à dire trop tard...

Microsoft ne fait pas que du soft pour micro... Depuis l'avènement de .NET il faut saluer les efforts importants qui sont fournis par MS pour fournir aussi de la matière grise. Labos de recherche, groupes de travail très indépendants, cette nouvelle orientation du management des équipes à permis l'éclosion d'un tas de bonnes idées. Tout ce travail est gracieusement délivré aux communautés de développeurs qui se donnent la peine de s'y intéresser...

Qui plus est, il ne s'agit pas d'élucubrations fumeuses. Les guides de bonnes pratiques, les conseils méthodologiques sont malgré tout le fruit d'un énorme travail collaboratif "au sommet" avec aux commandes des gens comme JD Meier, qui ne sont pas petites pointures !

Pour en revenir aux applications modulaires, il est essentiel de prendre connaissance de la V2 de PRISM, un recueil de codes, de documentations et de bonnes pratiques d'une qualité exceptionnelle.

Prism V2 c'est :

  • Une librairie pour la création d'applications composites
  • Une application de référence comme modèle d'implémentation (gestion de porte feuille boursier)
  • 9 "quick start" pour entrer dans le vif du sujet rapidement
  • 26 "how-to's" pour se former efficacement
  • Une documentation à la fois claire et riche

Prism supporte les applications WPF et Silverlight dans une même logique permettant de partager encore plus de code entre les deux types d'application.

Bref, je vous invite à vous pencher très sérieusement sur Prism si vous ne connaissez pas, pour tout projet d'une certaine envergure cela vous fera gagner beaucoup de temps, de productivité et le tout dans un cadre validé ne risquant pas de vous envoyer au mur.

Télécharger les éléments de Prism v2

Le site de Prism sur CodePlex

Bonne lecture !

Les guides de bonnes pratiques

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 !

 

Patterns & Practices : les guides de bonnes pratiques à connaître par coeur !

Dans la jungle du Framework et de tous ses projets satellites qui sortent au rythme d'une rafale d'AK47 comment un pauvre développeur isolé peut-il intégrer et digérer en quelques heures (en plus de son travail quotidien) des centaines, voire des milliers d'années-homme de librairies, technologies, outils et langages produits par Microsoft ?

C'est une véritable question. Dans mon propre travail de conseil je m'oblige à une réserve de 30% de mon temps uniquement dédié à la veille technologique, c'est à dire qu'un tiers de mon temps n'est jamais vendu, je me le réserve pour apprendre, un luxe indispensable mais coûteux en chiffre d'affaire potentiel perdu. De plus c'est un mode de fonctionnement que seul un dirigeant d'entreprise ou un indépendant peut s'offrir. Et malgré ce privilège tous les jours je mesure l'étendue de mon ignorance sur certains aspects du Framework avec la désagréable impression que plus je rame plus la côte s'éloigne ... Je suis certain de n'être pas le seul à ressentir cette sensation !

Il y a ceux qui ont d'emblée choisi de se spécialiser. Ils connaissent tout ou presque de Windows Forms, de WPF, ou de Silverlight mais ignore tout des dizaines d'autres éléments du Framework. Impasse sur ASP.NET, Ajax, MVC, Entity Framework, etc, etc. Chacun fait alors son petit marché n'ayant au final qu'une vue très restreinte sur le Framework, et, de fait, loupant souvent d'excellentes choses. Hélas une journée n'a que 24h, et pour avoir essayer par tous les moyens de contourner sans succès cette terrible réalité, je peux affirmer que travailler, même beaucoup, même trop, n'est pas forcément la solution. Seule Zenon d'Elée arrivait dans son paradoxe à faire gagner une tortue face à Achille ! Quand l'adversaire est en supériorité, la force brute est inutile, il faut ruser...

Microsoft a conscience de ce problème. Qu'il s'agisse des petites vidéo "how do I", des multiples conférences qui se tiennent régulièrement, de la documentation très fournie de MSDN, de l'excellent magazine MSDN toujours riche d'articles de haute tenue technique, et de bien d'autres actions en faveur de la diffusion de la connaissance sur ses produits, Microsoft fait beaucoup pour nous aider à appréhender l'étendue de sa gamme.

Si cet effort louable est important, on peut toujours en réclamer plus. Par exemple une certaine décentralisation de toutes ces informations fait que peu de gens connaissent tous les "points d'entrée" utiles de ces informations. Gageons que Microsoft en a aussi conscience et que des efforts supplémentaires seront réalisés pour aider le développeur "à s'y retrouver dans les informations qui permettent de s'y retrouver" dans les produits...

Patterns & Practices

Tout cela pour vous parler aujourd'hui des Patterns & Practices. Il s'agit d'un ensemble de recommandations et de code mis à disposition gratuitement sur CodePlex. Bien connaître ces "patrons et pratiques" peut vous aider à mieux tirer partie du Framework sans pour autant y consacrer vos nuits.

Pour obtenir la liste de tous les projets gravitant autour de ce concept de "patterns & practices" allez sur CodePlex et chercher cette expression. Vous pouvez aussi directement accéder à tous les projets pertinents par le tag du même nom (colonne de droite sur CodePlex).

Les projets clé

Il est bien entendu très difficile de créer un ordre de priorité dans tous les projets "patterns & practices", selon ce que vous développez, l'urgence de regarder tel ou tel projet sera plus ou moins grande. Mais je vais tenter une petite sélection de ceux qui, à mon sens, sont à connaître absolument.

App Arch Guide 2.0 Knowledge base

http://www.codeplex.com/AppArch 

Ce projet initié par JD Meier, Jason Taylor et Prashant Bansode regroupe de nombreux documents et vidéos dont le but est d'expliquer "How to put the legos together", ce dont je parlais plus haut : savoir comment utiliser correctement toutes les briques de constructions du Framework pour en faire quelque chose.

The purpose of the Application Architecture Guide 2.0 is to improve your effectiveness building applications on the Microsoft platform. The primary audience is solution architects and developer leads. The guide will provide design-level guidance for the architecture and design of applications built on the .NET Framework. It focuses on the most common types of applications, partitioning application functionality into layers, components, and services, and walks through their key design characteristics.

Ce projet est certainement le premier point d'entrée que vous devez connaître. Sa vision globale de l'architecture des applications vous aidera à prendre de meilleures décisions.

Application Architecture Guide 2.0 (le livre)

http://www.codeplex.com/AppArchGuide

Ce guide fournit des guides architecturaux pour la création d'applications .NET. Les principaux types d'applications sont étudiés et de nombreuses solutions sont proposées.

Le livre complète la "App Arch Guide 2.0 Knowledge base" décrite ci-dessus.

Enterprise Library

http://www.codeplex.com/entlib

http://msdn.microsoft.com/fr-fr/library/cc467894(en-us).aspx

L'Enterprise Library est une collection de blocs applicatifs qui ont été conçus pour assister le développeur dans son travail. Il s'agit de code mais d'une certaine façon ce code constitue un guide des bonnes pratiques.

Le code source est fourni avec une documentation. EL peut être utilisé tel quel ou bien modifié ou étendu.

Constitué de plusieurs "blocs", EL est une vraie mine d'or. Gestion du caching, cryptographie, accès aux données, gestion des exceptions, logging, sécurité, des solutions pratiques, génériques et éprouvées sont apportées à chacun de ses sujets.

Un "must have" donc. (et surtout un "must understand" !).

Web Client Software Factory

http://www.codeplex.com/websf

le WCSF fournit un ensemble de directives pour les architectes et les développeurs d'applications Web. La factory inclue des exemples, du code réutilisable et un ensemble de guidlines pour automatiser les tâches clé du développement sous Visual Studio. Pour ceux qui connaissaient, WCSF remplace UIP (User Interface Process Application Block). WCSF supporte bien entendu les nouveautés du Framework comme ASP.NET, Ajax ou Workflow Foundation.

A connaître dès lors qu'on veut développer des applications Web...

Composite WPF and Silverlight

http://www.codeplex.com/CompositeWPF

Ce bloc des patterns & practices fait partie du top 5 à connaître et maîtriser. Il intègre code et guidelines nécessaires à la mise en place d'architectures suivant les best practices pour les projets de type WPF et Silverlight. Tout est bon, il faut absolument le connaître !

A noter: ce projet est aussi connu sous le nom de "Prism" (info primordiale surtout pour faire le cador à la machine à café :-) ).

Smart Client Guidance

http://www.codeplex.com/smartclient

http://msdn.microsoft.com/en-us/library/aa480482.aspx

Le Smart Client Software Factory (SCSF) est une autre guide essentiel. Guidelines et code forment un ensemble de composants réutilisables sous Windows Forms autant que WPF ou ASP.NET pour mettre en place une architecture de client intelligents composites.

Je n'ai pas eu encore le temps de plonger dans ce guide, mais du survol que j'en ai fait, il faut très certainement le regarder de plus près, les solutions proposées semblent tout aussi indispensables à connaître que les autres guides de la série "patterns & practices".

Unity

http://www.codeplex.com/unity

Encore un guide que je n'ai pas eu le temps de lire... Les longues soirées d'hiver sont un mythe : il fait nuit plus tôt mais les journées ne comptent toujours pas plus de 24h, cette expression est donc une escroquerie ! :-)

The Unity Application Block (Unity) is a lightweight extensible dependency injection container with support for constructor, property, and method call injection.

Dit comme ça, c'est un peu confus... pour tout savoir le mieux c'est de lire (le conseil vaut pour moi aussi) !

WCF Security Guidance

http://www.codeplex.com/WCFSecurity

La mise en place de services via WCF peut se faire de façon très naïve... ou de façon professionnelle, c'est à dire en gérant correctement la sécurité ! Ce guide fait le tour de la question et propose des guidelines autant que du code pour mieux sécuriser les applications communiquantes et gérer correctement les authentifications, les autorisations et toutes ces choses indispensables pour des applications mises en production.

Web Service Software Factory

http://www.codeplex.com/servicefactory

Il s'agit d'une collection d'outils, de patterns, de code source et de guidelines destinés à vous aider à construire des Web services WCF et ASMX rapidement mais en garantissant la meilleure fiabilité possible.

Indispensable comme le reste...

Guidance Explorer

http://www.codeplex.com/guidanceExplorer

Voici peut être un moyen de s'y retrouver un peu mieux parmi toutes les guidelines ! le Guidance Explorer, comme son nom l'indique, est un outil d'exploration des guidelines. Une fois installé il se met à jour via le Web.

Centralisant bon nombre de guides et simplifiant l'accès à l'information, Guidance Explorer est l'une des premières choses à installer à côté de Visual Studio !

MS Health Common User Interface

http://www.codeplex.com/mscui

C'est un peu l'ovni de cette liste et même un vrai ovni à part entière dans tout le Framework et les guidelines. On se demande pourquoi Microsoft a investi dans cette branche très spécialisée plutôt qu'une autre. Le MSCUI est en effet un ensemble de guidelines, de code et de composants permettant de réaliser des application orientées médical. Composants WCF, Silverlight ou autres, c'est un ensemble incroyable quantitativement et qualitativement. Ayant été l'un des pioniers de l'informatique médicale en France avec la gamme de logiciel Hippocrate je connais particulièrement bien la question et j'ai été bluffé par ce que propose MS avec MSCUI. Avec cet outil un bon développeur peut assez rapidement mettre en place des solutions tout à fait honorables capables de concurrencer les principaux logiciels médicaux du marché...

Si le médical n'est pas votre branche, MSCUI ne vous intéressera que peu dans la pratique, mais regardez tout de même comment cela est fait et comment les composants se présentent, ergonomiquement et fonctionnellement c'est un beau travail.

Conclusion

Si ce tour d'horizon n'est pas exhaustif il contient malgré tout le top des guidelines et outils à connaître pour développer sereinement des solutions basées sur des patterns éprouvées. Ne pas réinventer la roue, mettre rapidement en place la bonne architecture d'un projet c'est déjà s'assurer à 50% de sa réussite.

Si les soirées d'hiver ne comportent pas plus d'heures que celles d'été, la froidure incite moins à sortir et à flâner qu'en août, profitez-en pour rester au chaud en vous plongeant dans les Patterns & Practices !

N'oubliez pas ce vieux proverbe qui nous vient du fond des âges :

"L'hiver, qui bouquine Patterns & Practice,
 l'été, produit du code qui glisse !"

Et Stay Tuned !

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)

La guerre des clones n'aura pas lieu ! ... Si vous possédez Clone Detective !

Clone Detective pour Visual Studio est un outil de debug un peu particulier puisqu'il ne cherche pas les bugs mais les redondances de code.

La redondance de code est un ennemi sournois... Le code dupliqué est bien souvent la porte ouverte aux inconsistances et aux regressions lors de mises à jour correctives ou évolutives. En effet, si un bug apparaît un jour dans une séquence mais qu'il n'a pas encore été repéré dans une autre fonction qui utilise pourtant un code similaire, la mise à jour ne concernera que la séquence incriminée. Un jour où l'autre la partie codée de façon similaire plantera aussi pour les mêmes raisons. Elle ne sera d'ailleurs peut être pas corrigée de la même façon ni par la même personne... Inconsistance. Coûts de maintenance inutiles. Les défauts de la redondance de code ne manque donc pas.

D'ailleurs un outil capable de retrouver ces fameux clones est un excellent assistant pour un auditeur... La présence des clones trahit non pas celle de Palpatine ou du comte Doku mais bien d'un code for mal refactoré, donc pauvre et qui laissera par force voir d'autres faiblesses !

Précisons que Clone Detective cherche les "clones", pas seulement le code purement redondant de type copier/coller. Il analyse le code en supprimant les commentaires, les espaces, les noms de variables etc, afin d'obtenir un code de référence le plus générique possible avant d'y chercher les clones. Les clones formes des classes de code équivalent, l'un pourrait remplacer l'autre sans problème, à la présentation et aux noms de variables près.

Il s'agit donc bien d'une recherche intelligente et non simplement d'une recherche de "doublons". Mais la lecture de la documentation de CD vous en dira bien plus que ces quelques lignes.

Donc pour se prémunir des dommages causés par les clones, et si on ne connait pas le numéro de portable de Yoda, le mieux est encore de télécharger Clone Detective. Bonne nouvelle il n'habite pas une galaxie lointaire, il est sur CodePlex et donc gratuit avec le code source pour les curieux ! Mieux, C.D. s'insère dans VS 2008 (voir / autres fenêtres, pour afficher celles de C.D. une fois installé). Vraiment un bel add-in dont il serait bête de se priver !

Pour accéder à la page de chargement, cliquez simplement sur le logo de Clone Detective inséré plus haut dans le corps de ce billet.

Que la force soit avec vous !

(et Stay Tuned !)