Un livre à avoir : "Framework Design Guidelines" 25. juillet 2008 Olivier C#, Framework .NET, Méthodologie (0) J'achète assez peu de livres en général, en effet dès qu'on s'intéresse à ce qui est nouveau tous les livres potentiellement intéressants sont "à paraître", et quand ils paraissent l'info est déjà réchauffée par rapport à ce qu'on peut trouver des mois avant sur Internet. Bien entendu cela implique d'aller chercher l'information brute, éparpillée et en anglais. C'est pour cela que les ouvrages techniques en français trouvent toute leur utilité en offrant une information structurée, vérifiée et traduite pour tous ceux qui ne lisent pas l'anglais. Toutefois il m'arrive d'acheter certains livres lorsqu'ils traitent de sujet plus "intemporels" comme la méthodologie ou ici le framework .NET. Oui, ce livre est en anglais, et à votre prochaine question la réponse est non, je n'en connais pas de traduction en français ce qui est de toute façon rare dans ce domaine. L'exemple récent de la fermeture de l'antenne française de O'Reilly illustre la difficulté globale de vendre du livre technique en France malgré un catalogue attrayant. Quant à traduire un ouvrage et donc réengager des frais importants pour un public très restreint, fort peu s'y sont risqués ou s'y risquent. Dans mes propres "development guidlines", en position 1 je mettrais "faites du C#", en 2 "faites du LINQ", mais en position 0 "faites de l'anglais" !. Framework Design Guidelines Ce livre traite des bonnes méthodes à mettre en oeuvre pour développer des applications respectueuses du framework .NET. Ecrit par deux membres de l'équipe de développement de .NET il a aussi été relu et corrigé par d'autres personnes de cette équipe qui, chose originale, font apparaître ici et là des petits encadrés où ils apportent leur point de vue sur ce que les auteurs viennent d'écrire. C'est une approche intéressante qui rend le contenu plus vivant. L'avant propos est d'ailleurs signé par Anders Hejlsberg, difficle de trouver mieux... Ce n'est pas un livre très récent, il a du paraître en 2006 la première fois, mais son contenu est "intemporel" dans le sens où il dépasse les modes ou les dernières options du framework pour parler de son coeur, ses API, sa structure et la façon de bien coder dans cet environnement. Le contenu Le sous titre est finalement clair "Conventions, Idioms and Patterns for Reusable .NET Libraries". Conventions, idiomes et patterns pour des bibliothèque de codes réutilisables. La première partie traite des "qualités d'un framework bien conçu" et on entre un peu dans les coulisses de .NET, comment et pourquoi certains choix ont été arretés. Une façon agréable, au delà du côté anecdotique, d'aborder les qualités générales que se doit posséder toute bonne bibliothèque de code, comme celles que nous sommes amenées parfois à écrire. Les fondamentaux du framework sont ensuite abordés puis on trouve les guidelines proprement dites. Stratégie de nommage, le choix entre classe ou structure, entre classe abstraite ou interface, la conception des membres d'une classe (propriétés, méthodes, champs...) tout cela est traité avec intelligence, à propos, et permet d'affiner ses propres vues sur ces questions ou parfois même de s'interroger sur des sujets dont on pensait avoir fait le tour. Les exceptions sont traitées en détail, toujours sous l'angle de la meilleure mise en oeuvre possible plus que sur le code ou la syntaxe. Ce livre est un moyen terme entre théorie, design pattern, expérience de développeurs ayant écrit l'un des plus gros framework existant, sagesse et doutes que tout informaticien conscient de l'importante de ses choix se doit d'avoir. Un final sur l'utilisation de FxCop, son évolution, comment il marche et comment en tirer le meilleur est tout à fait appréciable. Je parlais justement dans un dernier billet de la "sous utilisation" de cet outil fanstastique par la majorité des développeurs que je rencontre ou que je forme. Ce chapitre permettra peut-être aux lecteurs de ce livre de faire plus souvent usage de FxCop. Le CD Le livre est accompagné d'un CD contenant des exemples de code et plusieurs vidéo de conférences (en anglais aussi, ne rêvez pas :-) ), toutes particulièrement intéressantes et portant sur des sujets aussi variés que le garbage collector (et tout ce qui tourne autour de la libération de la mémoire et des ressources), les performances ou le packaging et le déploiement. Une vraie mine d'information, pas forcément récentes, comme le livre lui-même, mais qui abordent des fondamentaux parfois négligés. Conclusion Comme je le disais, ce livre est intemporel, en tout cas tant que vivra le framework .NET sous la forme que nous connaissons depuis plusieurs années maintenant. Il permet d'entrer un peu dans l'équipe de développement de cette plateforme, de comprendre les choix qui ont présidé à sa mise en oeuvre et surtout il permet, par cette vision "de l'intérieur" et les nombreux conseils qu'il contient de mieux concevoir son propre code, de mieux trancher entre certaines options parfois trop nébuleuses (classe ou interface, classe ou structure, par exemple) en sachant avec précision pourquoi on fera ou non tel ou tel choix. C'est un livre plaisant à lire, qu'on peut "consommer" en piochant les chapitres, pas forcément dans l'ordre du livre. Un "must have" pour qui développe sous .NET, c'est une évidence. Références Framework Design Guidelines Krzysztof Cwalina (au scrabble si vous le placez vous mettez à genou vos adversaires !) Brad abrams 0-321-24675-6 Edité par Addison-Wesley Une bonne librarie technique en ligne Vous pouvez acheter ce livre dans toutes les bonnes librairies, toutefois si vous voulez le payer le moins cher possible, je vous conseille de le commander directement à "The Book Depository Ltd", une librairies anglaise qui livre vite et bien qui affiche des prix en général très bas. Leur site Internet : http://www.bookdepository.co.uk/WEBSITE/WWW/WEBPAGES/homepage.php C'est un vendeur qu'utilise Amazon, mais si vous passez en direct c'est généralement moins cher... Bonne lecture et Stay Tuned !
Appel d'un membre virtuel dans le constructeur ou "quand C# devient vicieux". A lire absolument... 20. juillet 2008 Olivier C# (4) Une facette peu connue de C# qui peut mener à des bogues graves et difficiles à résoudre... [Plus]
C# MONO et OpenSuse, le tout virtualisé. Ou "comment occuper un geek" en une leçon. 12. juillet 2008 Olivier C#, Framework .NET (0) Il faut bien se détendre un peu... Et que fait un geek pour se détendre ? Il allume son PC (en fait il ne l'éteint jamais !). Alors pour me changer les idées, je me suis mis en tête d'installer MONO pour "voir" où le projet en est. C'est cette petite aventure que je m'en vais vous conter, ça pourra vous servir... Mais voilà, avant de jouer avec C# sous Linux il faut d'abord installer Linux (hmmm), choisir l'une de ses distribs et surtout arriver à le faire marcher ! Et comme je suis pervers mais pas fou, hors de question de faire un dual boot (j'en ai déjà un XP/Vista) ni même de reformater l'une de mes machines. Il va donc falloir virtualiser... Abonné MSDN je dispose de tout plein de softs de Microsoft, dont Virtual PC 2007. J'ai commencé petit joueur par une Mandriva. Impossible à installer, le fameux problème d'écran 24 bits par défaut de Linux... Une vraie cata. J'ai passé des heures à chercher sur le Web, on trouve des trucs, mais aucun ne marche ! Après des heures à tourner en rond face à un écran très élargi et illisible, j'ai renoncé. J'ai donc laissé Virtual PC de côté, super chouette pour virtualiser du DOS, de l'XP ou du Vista, mais visiblement pas très open à Linux... En farfouinant je suis tombé sur un virtualiseur gratuit écrit par l'ennemi juré de Microsoft, Sun. Du coup j'ai pas essayé de voir si leur machin savait lui gérer la virtualisation de Windows aussi mal que Virtual PC gère mal Linux, mais pour virtualiser du Linux ça semblait un bon choix. J'ai donc télécharger VirtualBox. Ca s'installe facilement, ça fait ce que ça dit et ça marche plutôt vite. Un bon point pour ce produit gratuit. J'entreprends alors de virtualiser mon Mandriva que je n'avais pu installer sous Virtual PC. Et là, surprise, ça passe facile. Mon coeur s'emplit de joie en voyant Linux tourner dans une fenêtre... Mandriva, hélas, c'est KDE. L'avantage de KDE c'est d'être un clône de Windows XP niveau interface. On s'y retrouve facilement. Mais voilà, je n'y avais pas pensé avant, le kit de développement de MONO réclame visiblement GNOME ! Zut! me suis-je exclamé après toutes ses heures (en réalité c'était pas "zut", mais un truc du même genre en plus .. illustré :-) ). [EDIT:] Il semble que le site de MONO ignore Mandriva, mais depuis le gestionnaire de logiciels de ce dernier on peut accéder au téléchargement de MONO et des outils de développement. Je viens de le faire et oui, ça marche parfaitement. On peut donc utiliser Mandriva sans problème [/EDIT]. [EDIT2:] Comme je suis une quiche sous Linux, je n'avais pas pigé non plus que Mandriva permet de changer de bureau et de système d'affichage. On peut donc être en KDE ou GNOME sans problème la non plus. C'est en bûchant qu'on devient bucheron n'est-il pas... [/EDIT2] Je farfouine à nouveau... et au final je me décide pour OpenSuse, promu par Novell qui se trouve derrière MONO aussi, ça devrait matcher. Bonne pioche ! Je prend la 11.0 mais ils préviennent que les fichiers de plus 4 Go posent des problèmes en téléchargement HTTP depuis Internet Explorer. J'avais téléchargé des DVD ISO il y a peu de temps avec IE sans problème et je me doutais bien qu'il s'agissait là de médisances de concurrents, mais par précaution j'ai pris l'option téléchargement par Torrent. Deux ou trois heures après j'avais mon ISO. ...Et on est reparti pour la création d'une machine virtuelle. Au passage je me dis, tiens, pourquoi ne pas redonner sa chance à Virtual PC, après tout. Je vous passe les détails, une heure de perdue pour exactement le même problème sans arriver à trouver une solution. Retour à VirtualBox. Là les choses se passent comme avec Mandriva, c'est à dire bien et facilement. Installation de OpenSuse. C'est pas mal. J'arrive ensuite à faire marcher l'installeur YaST pour ajouter les références au projet Mono et à installer l'ensemble des paquets. Cool. J'ai maintenant un joli OpenSuse avec MONO et l'environnement de développement ! Je fais un nouveau projet console et je tape deux ou trois truc pour voir. Pas mal. C'est du niveau C# 2.0 avec les génériques. Je n'ai pas encore creusé plus loin, mais voir mon petit programme s'animer dans une fenêtre Linux sur mon XP, ça fait plaisir... J'ai voulu aller un peu plus loin en partageant un disque physique de ma machine avec la machine virtuelle. VirtualBox le permet en installant des extensions. Retour à la galère, il faut les sources du noyau, le make et GCC. Trouver ces merveilles, les placer dans YaST et les installer s'est révélé moins dur que je le pensais. Mais la fameuse extension de VirtualBox n'a jamais voulu s'installer... C'est un script (.run) et quand je double cliquait dessus il me disait que j'avais pas les privilège admin ce qui plantait le script visiblement. Pourtant le compte que je me suis créé est dans le group des admins... Mystère de Linux. En changeant de user et en me loggant comme "root" c'est allé un cran plus loin. Mais là c'est un autre problème un peu confus que je n'ai pas pu résoudre qui s'est pointé... VirtualBox et ses extensions ne sont donc pas trop au point si on veut utiliser toutes les astuces. [EDIT] Devant l'impossibilité de partager un disque XP avec la VM j'ai essayé de faire un partage réseau... Mandriva ou OpenSus voient le réseau et Internet, mais malgré tous mes efforts à ce jour, impossible de leur faire voir les autres machines du réseau et encore moins leurs disques, malgré le daemon Lisa, malgré Samba et autres pares-feux aux configurations ésotériques... Si quelqu'un sait... laissez un commentaire![/EDIT] Conclusion Les leçons à tirer sont les suivantes : Il faut utilise VirtualBox et non Virtual PC si on veut virtualiser du Linux, Il faut prendre une distrib Linux compatible avec Mono, OpenSuse semble parfaite pour ça [EDIT] Mandriva va très bien aussi [/EDIT]. Sinon pour le reste ça s'installe correctement, YaST simplifiant les choses (mais il faut comprendre comment marche YaST, notamment l'ajout d'une source d'installation, c'est là l'astuce !). Les dernières moutures de Linux, Suse ou Mandriva sont des pâles copies de Windows XP, en moins chouette visuellement, et en plus complexe à faire marcher. Esthétiquement ça fait un peu Matrix, parfois l'interface graphique disparaît (lancement et extinction par exemple) et on voit des trucs défilés dans une console, très geek mais pas très "end user" donc. Toutefois il faut saluer les gros efforts de ces dernières années pour rendre Linux utilisable, mais on l'impression d'un truc "cheap", on voit bien que c'est gratuit quoi... Surtout quand on utilise Vista, dont on peut dire ce qu'on veut, mais quand on en a pris l'habitude, c'est autrement plus joli et plus agréable que XP ou les distribs Linux. Les fous de la customisations diront qu'ils peuvent installer des bureaux super chouettes sous Linux, c'est vrai. Mais que d'effort pour arriver à faire, de façon compliquée, ce qu'un Vista fait de base de façon simple... Reste que la concurrence Linux est une bonne chose, et les essais que je vous conte ici sont la preuve que j'attache de l'importance à cette alternative même si elle est loin de me convaincre, pour le moment, de reformater toutes mes machines sous Linux. Mais bon, le truc ce n'était pas de tester Linux ni de donner mon avis sur la question, Linux est un monde fermé, si un "vendu" à la cause Microsoft fait des réserves c'est forcément qu'il est corrompu et donc que son avis n'a pas d'intérêt. Les Linuxiens ne lisent donc de toute façon pas les avis des non linuxiens sur Linux. Non, mon but était d'installer MONO et de faire des tests. De ce côté là c'est concluant. Juste une chose qu'il me reste à creuser, la gestion des fenêtres (l'équilavent de Windows Forms) en tout cas dans la version dont je dispose, utilise un truc vraiment très différent donc pas portable du tout. Il va falloir que je regarde s'il existe une émulation des Windows Forms sinon cela limite la compatibilité entre .NET et MONO aux libs de classes. C'est déjà pas mal, mais ça me semble trop juste. [Edit:] Il y a bien un System.Windows.Forms dans les libs installées, mais point de designer dans l'IDE.. Il semble qu'il en existe un, mais j'ai pas tout pigé comment l'obtenir, est ce un truc à part, un plugin, en tout cas ça demande d'obtenir le source de MONO par svn et de tout recompiler.. Brrr. L'esprit Linux c'est, il faut l'avouer, très très éloigné des "user experiences" de MS ! [/Edit] Voilà pour cette petite histoire, qui, au fil des paragraphes vous aura peut-être donné l'envie de tenter l'expérience aussi, en utilisant directement les bonnes solutions et sans perdre de temps :-) avant de lâcher mon traditionnel "Stay Tuned!" voici une petite image, ça fait toujours plaisir : OpenSuse 11.0 avec MonoDevelop Sous les pavés la plage... Chez moi c'est vrai, mais en plus sous la fenêtre OpenSuse il y a aussi le bureau XP avec VirtualBox (et l'icone de lancement de VS 2008 mon compagnon de tous les jours) ! Cool isn't it ? Alors... pour d'autres aventures : Stay Tuned ! [EDIT:] Comme j'ai réussi à faire pareil sous Mandriva, une petite image aussi ![/EDIT]
De l'intérêt d'overrider GetHashCode() 11. juillet 2008 Olivier C#, Framework .NET (2) Les utilisateurs de Resharper ont la possibilité en quelques clics de générer un GetHashCode() et d'autres méthodes comme les opérateurs de comparaison pour toute classe en cours d'édition. Cela est extrêment pratique et utile à plus d'un titre. Encore faut-il avoir essayer la fonction de Resharper et s'en servir à bon escient... Mais pour les autres, rien ne vient vous rappeler l'importance de telles fonctions. Pourtant elles sont essentielles au bon fonctionnement de votre code ! GetHashCode() Cette méthode est héritée de object et retourne une valeur numérique sensée être unique pour une instance. Cette unicité est toute relative et surtout sa répartition dans le champ des valeurs possibles est inconnue si vous ne surchargez pas GetHashCode() dans vos classes et structures ! Il est en effet essentiel que le code retourné soit en rapport direct avec le contenu de la classe / structure. Deux instances ayant des valeurs différentes doivent retourner un hash code différent. Mieux, ce hash code doit être représentatif et générer le minimum de collisions... Si vous utilsez un structure comme clé d'une Hashtable par exemple, vous risquez de rencontrer des problèmes de performances que vous aurez du mal à vous expliquer si vous n'avez pas conscience de ce que j'expose ici... Je ne vous expliquerais pas ce qu'est un hash code ni une table Hashtable, mais pour résumer disons qu'il s'agit de créer des clés représentant des objets, clés qui doivent être "harmonieusement" réparties dans l'espace de la table pour éviter les collisions. Car en face des codes de hash, il y a la table qui en interne ne gère que quelques entrées réelles. S'il y a collision, elle chaîne les valeurs. Moralité, au lieu d'avoir un accès 1->1 (une code hash correspond à une case du tableau réellement géré en mémoire) on obtient plutôt n -> 1, c'est à dire plusieurs valeurs de hash se partageant une même entrée, donc obligation de les chaîner, ce que fait la Hashtable de façon transparente mais pas sans conséquences ! Il découle de cette situation que lorsque vous programmez un accès à la table de hash, au lieu que l'algorithme (dans le cas idéal 1->1) tombe directement sur la cellule du tableau qui correspond à la clé (hash code), il est obligé de parcourir par chaînage avant toutes les entrées correspondantes... De là une dégration nette des performances alors qu'on a généralement choisi une Hashtable pour améliorer les performances (au lieu d'une simple liste qu'il faut balayer à chaque recherche). On a donc, sans trop le savoir, recréé une liste qui est balayée là où on devrait avoir des accès directs... La solution : surcharger GetHashCode() Il existe plusieurs stratégies pour générer un "bon" hash code. L'idée étant de répartir le plus harmonieusement les valeurs de sorties dans l'espace de la table pour éviter, justement, les collisions de clés. Ressortez vos cours d'informatique du placard, vous avez forcément traité le sujet à un moment ou un autre ! Pour les paresseux et ceux qui n'ont pas eu de tels cours, je ne me lancerais pas dans la théorie mais voici quelques exemples d'implémentations de GetHashCode() pour vous donner des idées : La méthode "bourrin" Quand on ne comprends pas forcément les justifications et raisonnements mathématiques d'un algorithme, le mieux est de faire simple, on risque tout autant de se tromper qu'en faisant compliqué, mais au moins c'est facile à mettre en oeuvre et c'est facile à maintenir :-) Imaginons une structure simple du genre : public struct MyStruct { public int Entier { get; set; } public string Chaine { get; set; } public DateTime LaDate { get; set; } } Ce qui différencie une instance d'une autre ce sont les valeurs des champs. Le plus simple est alors de construire une "clé" constituée de toutes les valeurs concaténées et séparées par un séparateur à choisir puis de laisser le framework calculer le hash code de cette chaîne. Toute différence dans l'une des valeurs formera une chaine-clé différente et par conséquence un hash code différent. Ce n'est pas super subtile, mais ça fonctionne. Regardons le code : public string getKey() { return Entier + "|" + Chaine + "|" + LaDate.ToString("yyyyMMMddHHmmss"); } public override int GetHashCode() {return getKey().GetHashCode(); } J'ai volontairement séparé la chose en deux parties en créant une méthode getKey pour pouvoir l'afficher. La sortie (dans un foreach) de la clé d'un exemple de 5 valeurs avec leur hash code donne : 1|toto|2008juil.11171952 Code: -236695174 10|toto|2008juil.11171952 Code: -785275536 100|zaza|2008juil.01171952 Code: -684875783 0|kiki|2008sept.11171952 Code: 888726335 0|jojo|2008sept.11171952 Code: 1173518366 La méthode Resharper Ce merveilleux outil se propose de générer pour vous la gestion des égalités et du GetHashCode, laissons-le faire et regardons le code qu'il propose (la structure a été au passage réécrite, les propriétés sont les mêmes mais elles utilisent des champs privés) : D'abord le code de hachage : public override int GetHashCode() { unchecked { int result = entier; result = (result*397) ^ (chaine != null ? chaine.GetHashCode() : 0); result = (result*397) ^ laDate.GetHashCode(); return result; } } On voit ici que les choix algorithmiques pour générer la valeur sont un peu plus subtiles et qu'ils ne dépendent pas de la construction d'une chaîne pour la clé (ce qui est consommateur de temps et de ressource). Profitons-en pour regarder comment le code gérant l'équalité a été généré (ainsi que le support de l'interface IEquatable<MyStruct> qui a été ajouté à la définition de la structure) - A noter, la génération de ce code est optionnel - : public static bool operator ==(MyStruct left, MyStruct right) { return left.Equals(right); } public static bool operator !=(MyStruct left, MyStruct right) { return !left.Equals(right); } public bool Equals(MyStruct obj) { return obj.entier == entier && Equals(obj.chaine, chaine) && obj.laDate.Equals(laDate); } public override bool Equals(object obj) { if (obj.GetType() != typeof(MyStruct)) return false; return Equals((MyStruct)obj); } Bien que cela soit optionel et n'ait pas de rapport direct avec GethashCode, on notera l'intérêt de la redéfinition de l'égalité et des opérateurs la gérant ainsi que le support de IEquatable. Une classe et encore plus une structure se doivent d'implémenter ce "minimum syndical" pour être sérieusement utilisables. Sinon gare aux bugs difficiles à découvrir (en cas d'utilisation d'une égalité même de façon indirecte) ! De même tout code correct se doit de surcharger ToString(), ici on pourrait simplement retourner le champ LaChaine en supposant qu'il s'agit d'un nom de personne ou de chose, d'une description. Tout autre retour est possible du moment que cela donne un résultat lisible. Ce qui est très pratique si vous créez une liste d'instances et que vous assignez cette liste à la propriété DataSource d'un listbox ou d'une combo... Pensez-y ! Conclusion Créer des classes ou des structures, si on programme sous C# on en a l'habitude puisque aucun code ne peut exister hors de telles constructions. Mais "bien" construire ces classes et structures est une autre affaire. Le framework propose notamment beaucoup d'interfaces qui peuvent largement améliorer le comportement de votre code. Nous avons vu ici comment surcharger des méthodes héritées de object et leur importance, nous avons vu aussi l'interface IEquatable. IDisposable, INotityPropertyChanged, ISupportInitialize, et bien d'autres sont autant d'outils que vous pouvez (devez ?) implémenter pour obtenir un code qui s'intègre logiquement au framework et en tire tous les bénéfices. Bon dev, et Stay Tuned !
Les class helper : s'en servir pour gérer l'invocation des composants GUI en multithread 9. juillet 2008 Olivier C# (0) Les class helper dont j'ai déjà parlé ici peuvent servir à beaucoup de choses, si on se limite à des services assez génériques et qu'on ne s'en sert pas pour éclater le code d'une application qui deviendra alors très difficile à maintenir. C'est l'opinion que j'exprimais dans cet ancien billet et que je conserve toujours. Dès lors trouver des utilisations pertinentes des class helpers n'est pas forcément chose aisée, pourtant il ne faudrait pas les diaboliser non plus et se priver des immenses services qu'ils peuvent rendent lorsqu'ils sont utilisés à bon escient. Dans le blog de Richard on trouve un exemple assez intéressant à plus d'un titre. D'une part il permet de voir que les class helpers sont des alliés d'une grande efficacité dès qu'il s'agit de trouver une solution globale à un problème répétitif. Mais d'autre part cet exemple, par l'utilisation des génériques et des expressions lambda, a l'avantage de mettre en scène tout un ensemble de nouveautés syntaxiques de C# 3.0 en quelques lignes de code. Et les de ce genre sont toujours formateurs. Pour ceux qui lisent l'anglais, allez directement sur le billet original en cliquant ici. Pour les autres, voici non pas un résumé mais une interprétation libre sur le même sujet : Le problème à résoudre : l'invocation en multithread. Lorsqu'un thread doit mettre à jour des composants détenus par le thread principal cela doit passer par un appel à Invoke car seul le thread principal peut mettre à jour les contrôles qu'il possède. Cette situation est courante. Par exemple un traitement en tâche de fond qui doit mettre à jour une barre de progression. Bien entendu il ne s'agit pas de bricoler directement les composants d'une form depuis un thread secondaire, ce genre de programmation est à proscrire, mais même en créant dans la form une propriété publique accessible au thread, la modification de cette propriété se fera à l'intérieur du thread secondaire et non pas dans le thread principal... Il faut alors détecter cette situation et trouver un moyen de faire la modification de façon "détournée", c'est à dire de telle façon à ce que ce soit le thread principal qui s'en charge. Les Windows Forms et les contrôles conçus pour cette librairie mettent à la disposition du développeur la méthode InvokeRequired qui permet justement de savoir si le contrôle nécessite l'indirection que j'évoquais plus haut ou bien s'il est possible de le modifier directement. Le premier cas correspond à une modification depuis un thread secondaire, le dernier à une modification du contrôle depuis le thread principal, cas le plus habituel. La méthode classique Sous .NET 1.1 le framework ne détectait pas le conflit et les applications mal conçues pouvait planter aléatoirement si des modifications de contrôles étaient effectuées depuis des threads secondaires. Le framework 2.0 a ajouté plus de sécurité en détectant la situation qui déclenche une exception, ce qui est bien préférable aux dégâts aléatoires... Donc, pour s'en sortir on doit écrire un code du genre de celui-ci : [...] NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged); [...] delegate void SetStatus(bool status); void NetworkChange_NetworkAddressChanged(object sender, EventArgs e) { bool isConnected = IsConnected(); if (InvokeRequired) Invoke(new SetStatus(UpdateStatus), new object[] { isConnected }); else UpdateStatus(isConnected); } void UpdateStatus(bool connected) { if (connected) this.connectionPictureBox.ImageLocation = @"..\bullet.green.gif"; else this.connectionPictureBox.ImageLocation = @"..\bullet.red.gif"; } [...] Cette solution classique impose la création d'un délégué et beaucoup de code pour passer d'une modification directe à une modification indirecte selon le cas. Bien entendu le code en question doit être dupliqué pour chaque contrôle pouvant être modifié par un thread secondaire... C'est assez lourd, convenons-en... (l'exemple ci-dessus provient d'un vieux post de 2006 d'un blog qui n'est plus actif mais que vous pouvez toujours visiter en cliquant ici). Pour la compréhension, le code ci-dessus change l'image d'une PictureBox pour indiquer l'état (vert ou rouge) d'une connexion à un réseau et le code appelant cette mise à jour de l'affichage peut émaner d'un thread secondaire. Comme on le voit, la méthode est fastidieuse et va avoir tendance à rendre le code plus long, moins fiable (coder plus pour bugger plus...), et moins maintenable. C'est ici que l'idée d'utiliser un class helper prend tout son intérêt... La solution via un class helper La question qu'on peut se poser est ainsi "n'existe-t-il pas un moyen générique de résoudre le problème ?". De base pas vraiment. Mais avec l'intervention d'un class helper, si, c'est possible (© Hassan Céhef - joke pour les amateurs des "nuls"). Voici le class helper en question : public static TResult Invoke<T, TResult>(this T controlToInvokeOn, Func<TResult> code) where T : Control { if (controlToInvokeOn.InvokeRequired) { return (TResult)controlToInvokeOn.Invoke(code); } else { return (TResult)code(); } } Il s'agit d'ajouter à toutes les classes dérivées de Control (et à cette dernière aussi) la méthode "Invoke". Le class helper, tel que conçu ici, prend en charge le retour d'une valeur, ce qui est pratique si on désire lire la chaîne d'un textbox par exemple. Le premier paramètre "ne compte pas", il est le marqueur syntaxique des class helpers en quelque sorte. Le second paramètre qui apparaitra comme le seul et unique lors de l'utilisation de la méthode est de type Func<TResult>, il s'agit ici d'un prototype de méthode. Il sera donc possible de passer à Invoke directement un bout de code, voire une expression lambda, et de récupérer le résultat. Un exemple d'utilisation : string value = this.Invoke(() => button1.Text); Ici on va chercher la valeur de la propriété Text de "button1" via un appel à Invoke sur "this", supposée ici être la form. Le résultat est récupéré dans la variable "value". On note l'utilisation d'une expression lambda en paramètre de Invoke. Mais si le code qu'on désire appeler ne retourne pas de résultat ? Le class helper, tel que défini ici, ne fonctionnera pas puisqu'il attend en paramètre du code retournant une valeur (une expression). Il est donc nécessaire de créer un overload de Invoke pour gérer ce cas particulier : public static void Invoke(this Control controlToInvokeOn, Func code) { if (controlToInvokeOn.InvokeRequired) { controlToInvokeOn.Invoke(code); } else { code(); } } Avec cet overload la solution est complète et gère aussi bien le code retournant une valeur que le code "void". On peut écrire alors: this.Invoke(() => progressBar1.Value = i); Sachant que pour simplifier l'appel est ici effectué dans la form elle-même (this). L'appel à Invoke contient une expression lambda qui modifie la valeur d'une barre de progression. Mais peu importe les détails, c'est l'esprit qui compte. Conclusion Les class helpers peuvent fournir des solutions globales à des problèmes récurrents. Utilisés dans un tel cadre ils prennent tout leur sens et au lieu de rendre le code plus complexe et moins maintenable, au contraire, il le simplifie et centralise sa logique. L'utilisation des génériques, des prototypes de méthodes et des expressions lambda montrent aussi que les nouveautés syntaxiques de C#, loin d'être des gadgets dont on peut se passer forment la base d'un style de programmation totalement nouveau, plus efficace, plus sobre et plus ... générique. L'exemple étudié ici illustre parfaitement cette nouvelle façon de coder de C# 3.0 et les avantages qu'elle proccure à ceux qui la maîtrise. Bon dev ! (et Stay Tuned, of course !)