Dot.Blog

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

Astuce du jour : chercher des ebook avec google

Légal / pas légal, that is the question... Sur le Web on trouve de tout, à vous de savoir ce que vous téléchargez.

Je ne suis pas là pour vous faire la morale mais plutôt pour vous donner une astuce sur la syntaxe de google qui permet de retrouver facilement des ebook.

Pour chercher des ebook ajouter tout simplement la ligne qui suit à votre recherche :

-inurl:htm -inurl:html intitle:"index of" +("/ebooks""/book") +(chm pdf zip)

La syntaxe peut être facilement adaptée. On voit que l'astuce consiste à cherche dans les URL la présence de certains mots comme ebook ou book et plus spécifiquement des répertoires de sites Web s'appelant comme ça. On remarquera aussi la fin de la recherche permettant de chercher chm pdf et zip. On peut supprimer zip ou chm si on ne veut que des pdf ou ajouter d'autres extensions qu'on recherche.

Bonne lecture !

J'ai testé pour vous Windows 7 !

Testez la bêta de la nouvelle version du système d’exploitation : Windows 7 !

l'Annonce Microsoft

"Annoncé par Steve Balmer lors du keynote du CES 2009 de Las Vegas, la bêta de Windows 7 est maintenant disponible sur le site Windows. Cette bêta vous permettra de vous faire une idée concrète de ce que sera  le prochain système d’exploitation de Microsoft: de meilleures performances, encore plus de simplicité, tout en permettant à l’utilisateur professionnel et particulier de vivre de nouvelles expériences numériques ! Découvrez la nouvelle interface, le nouveau centre de gestion des périphériques, la « recherche unifiée » ou encore la fonction « homegroup » pour créer un réseau chez soi ou pour sa TPE en quelques clics . Dépêchez vous seuls 2,5 millions de bêta sont disponibles pour le grand public au niveau mondial ! " (source Microsoft)

Mon avis

Installation sous Virtual PC (ou Hyper V si vous l'avez) 

N'ayant pas de machine libre pour "jouer" avec une bêta j'ai choisi l'option Virtual PC 2007 (que Microsoft diffuse gratuitement, n'oubliez pas le SP1 non plus). Comme W7 n'est pas encore reconnu il suffit lors de la création d'une nouvelle machine virtuelle de choisir Vista. Choisissez un emplacement, une taille raisonnable pour la RAM et le disque (j'ai testé avec 1 Go dédié à W7 et un fichier de 20 Go). Au premier boot la machine virtuelle vous dira comme n'importe quelle PC qu'elle ne trouve pas le boot (ou bien stoppez la recherche de boot réseau si jamais ça cherche trop longtemps). Allez dans les options de VPC et montez le fichier ISO comme un CD. Et là ça boote et ça installe W7 sans l'ombre d'un souci. Une fois l'OS installé complètement, installez aussi (par les menus de VPC) les extensions de la machine virtuelle. Cela permet entre autre une utilisation plus fluide de la souris. Une fois l'OS arrêté, vous pouvez aussi regardez les réglages de la machine virtuelle dans VPC, par exemple la connexion réseau si la votre n'est pas reconnue d'emblée. Pour moi il n'y a eu aucun problème mon réseau a été reconnu tout de suite sans avoir à toucher aux réglages.

Ce que j'en pense

Ceux qui ont refusé d'installer Vista seront soit très contents, soit très déçus. Déçus s'ils s'attendent à "autre chose" que Vista, car W7, comme je l'ai dit maintes fois, c'est avant tout un Vista. Contents, ils le seront si ce qui les gênait dans Vista c'était une certaine lourdeur, car si W7 c'est un Vista, c'est un Vista revu et corrigé, amélioré et surtout plus rapide.

Sous Virtual PC la carte graphique détectée est une S3 Trio, un truc très vieux, vraiment minimaliste, donc pas d'effets 3D ni de jolies transparences. Si vous voulez voir W7 dans toute sa splendeur il faudra l'installer sur une vraie machine et pas en virtuel.

En dehors de l'absence des effets cosmétiques, le test sous Virtual PC a un grand mérite : faire fonctionner l'OS avec peu de RAM, une simulation de disque dur plus lente qu'un vrai disque, une carte graphique pourrie, osons le dire, et assez peu de puissance processeur, le tout pendant que XP ou Vista (selon votre config) tourne en dessous... Pire comme conditions de test c'est dur à faire, même un portable grand public à 400 euros fait mieux aujourd'hui...

Et bien j'ai été très surpris du peu de mémoire consommé (384 Mo) ainsi que de la vitesse d'exécution ! Windows 7 c'est un Vista qui ne passerait pas les contrôles anti-dopage (sauf sur le Tour de France, mais là c'est normal) !

Rapide, peu gourmant, et beaucoup de petites modifications ergonomiques très agréables et intelligentes. C'est un Vista en mieux donc. Tout ce qu'on pouvait reprocher à Vista est parti, et tout ce qui faisait de Vista un très bon OS est resté ou a été amélioré.

Internet Explorer 8 fait partie de la livraison et je n'avais pas eu le temps de le tester jusqu'à là, l'occasion a donc été idéale. Conclusion pour IE8, la même que pour Windows 7 vis à vis de Vista : c'est de l'IE7 en mieux, en plus rapide, et avec des améliorations ergonomiques vraiment bien. On trouve enfin une barre de recherche pour Ctrl-F, seul truc dinosauresque qui m'agaçait vraiment sous IE7.

Côté réseau, mon réseau local a été vu tout de suite sans problème, même entre l'hôte XP et la machine virtuelle Windows 7, ainsi qu'avec toutes les autres machines du réseau. Les petits problèmes rencontrés avec Vista à ce sujet sont donc oubliés.

En terme de vitesse, l'accès et l'affichage des pages Web complexes sous IE8/Windows 7 en machine virtuelle était 2 à 3 fois plus rapide que sous l'OS natif avec IE7 ! Quelle est la part de IE8 et celle de W7 dans ces performances je n'en sais rien, mais tout cela est vraiment rassurant.

Conclusion

Sous ma machine dual boot XP/Vista je suis maintenant très impatient de pouvoir effacer et reformater sous Windows 7 ma partition Vista ! Windows 7 s'avère rapide, peu gourmand, agréable à utiliser. Je conserverais certainement encore ma partition XP pour toutes les vieilleries (et pour certains dossiers clients), mais il est clair que dès la mise sur la marché de la finale de Windows 7 il deviendra mon OS par défaut.

Les liens de téléchargement

http://www.microsoft.com/france/windows/products/windowsvista/enterprise/default.mspx

http://technet.microsoft.com/fr-fr/evalcenter/dd353205.aspx

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 !

 

9 raisons de plus d'utiliser WPF !

Dans mon dernier article paru en décembre, 10 bonnes raisons de choisir WPF (téléchargement ici), je vous proposais sous la forme d'une introduction à WPF ce qui me semblait de bonnes raisons de choisir cet environnement pour vos nouvelles applications. Dario Airoldi de Microsoft Italie nous propose aujourd'hui ses 9 raisons de préférer WPF et ce ne sont pas forcément les mêmes que les miennes, ce qui allonge significativement la liste et vaut un petit détour.

Je ne vais ni traduire cet article (suivez le lien juste au-dessus) ni réviser le mien en ajoutant ces nouvelles raisons, mais voici un résumé des raisons de Dario, elles sont intéressantes :

Raison 1

L'analyste fonctionnel et le graphiste peuvent définir l'interface d'un logiciel par le biais d'un langage commun, XAML, et non plus par des bitmaps et des documents écrits que le développeur devait traduire en code. Avec WPF, développeur et designer peuvent travailler sur une base commune et des documents directement utilisables par les uns et les autres sans "traduction", ce qui diminue grandement les risques de confusion.
[OD]WPF n'impose pas systématiquement le travail d'un graphiste, mais pour obtenir une interface de grande qualité graphique une telle présence s'avère indispensable. Il ne s'agit donc pas d'une obligation de WPF mais bien d'une exigence de qualité de l'expérience utilisateur. Vous avez le droit de faire des choses très laides de type Win32 en WPF si cela vous chante ou vous passer d'un infographiste si vous êtes aussi doué au pinceau qu'en C# ![/OD]

Raison 2

La quantité de code C# ou VB est grandement réduite grâce à WPF. Toute l'interface est gérée soit par du XAML soit par du Data Binding.

Raison 3

La séparation entre l'interface utilisateur et la logique métier est nette et franche.

Raison 4

Le système de routage des commandes de WPF permet de découpler l'implémentation d'une action de l'objet émettant la commande.
[OD] Je n'ai pas développé cet aspect de WPF dans mon article, c'est un tort ! [OD]

Raison 5

Le chargement dynamique de code XAML rend possible l'adaptation à la volée de l'interface utilisateur, par exemple selon le rôle de l'utilisateur, son groupe de travail, son profil...

Raison 6

Les validations côté client et côté serveur sont largement simplifiées par l'architecture de validation du Data Binding et par le système de routage des événements.

Raison 7

Les applications WPF sont plus faciles à maintenir et plus flexibles.
[OD] Cela découle en partie des raisons précédentes et de la nature même de WPF et des outils qui gravitent autour comme Expression Blend par exemple.[/OD]

Raison 8

Le templating des contrôles et les styles permettent de créer des interfaces sophistiquées tout en restant humainement maintenables.
[OD] WPF a été créé en intégrant le graphisme, le multimédia, les animations, la 3D, etc... Avec WPF on personnalise l'interface à l'extrême si besoin tout en utilisant des composants standard et sans nécessité d'acquérir des bibliothèques tierces, impossibles à maintenir, chères et incompatibles entres elles. [/OD]

Raison 9

WPF fonctionne sur la base d'un système de coordonnées déconnecté de la résolution des écrans ce qui rend l'adaptation au matériel bien plus simple.
[OD] Il fut un temps où tout le monde fonctionnait avec "la" norme de l'instant, comme VGA par exemple. Aujourd'hui l'utilisateur a un choix énorme d'écrans aux proportions, tailles et résolutions différentes, sans parler des unités mobiles et autres smartphones ! WPF apporte une solution simple à ce problème. [/OD]

Conclusion

WPF facilite le développement d'applications plus sophistiquées et plus ergonomiques en écrivant moins de code.
[OD] Tout cela est vrai. Mais n'oublions pas que WPF impose aussi une solide formation car la façon de travailler sous cet environnement est très différente de ce qu'on connaissait. Nier cet aspect des choses ne serait pas honnête et c'est ce qui explique que beaucoup de développeurs n'ont pas encore sauté le pas. [/OD]

Pour les détails de l'exposé (sans mes commentaires) je vous conseille la lecture du billet original.

Encore une fois bonne année à tous... et Stay Tuned !

Nomination Microsoft Most Valuable Professional (MVP) 2009 !

L'année commence bien ! J'ai le plaisir de vous annoncer qu'en ce premier janvier 2009 je viens de recevoir ma nomination MVP C#.

Je mesure l'honneur qui m'est fait au travers de cette nomination et je vais continuer à oeuvrer, au travers de ce blog, des articles que je publie et des livres en cours de rédaction, pour mériter ce titre en partageant toujours plus d'information technique mais aussi ma passion en mon métier et ma conviction que les outils Microsoft depuis la naissance de .NET marquent un tournant décisif autant qu'un bond qualitatif jamais réalisé en matière d'environnement de développement et d'experience utilisateur.

Je vous souhaite à tous une année 2009 exaltante, tant sur le plan professionnel que personnel !

Le site MVP où vous pouvez consulter ma nomination

 

10 bonnes raisons de choisir WPF (nouvel article à télécharger)

WPF cet inconnu... Alors que cette technologie est disponible depuis deux ans elle semble peiner à s'imposer parmi les développeurs. Je me suis demandé pourquoi et je crois que WPF paye un peu son image du "tout graphique hyper looké de la mort", des démos où l'on voit des vidéos danser en l'air sous forme de carrousel, de pages qui se plient comme un livre pour passer d'une fiche à l'autre et autres débauches d'effets spéciaux.Plus...

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)