Dot.Blog

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

De l'intérêt d'overrider GetHashCode()

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 !

La bombe Parallèle ! PLINQ, et PCP Parallel Computing Platform

Le framework .NET n'en finit pas d'innover ! La bombe du jour c'est PLINQ et PCP.

Vous ne connaissez pas encore ? Normal, ce n'est même pas encore sorti, juste en preview...

Pour résumer en un mot : Révolutionnaire. Comme LINQ.

L'objectif

Vous l'avez certainement remarqué comme tout geek qui se respecte, depuis quelques années nos amis fondeurs ne se battent plus à coup de Ghz pour promouvoir leurs microprocesseurs... Les gammes se multiplient et surtout les coeurs ! Et le temps où le concepteur d'un soft trop lent pouvait toujours dire "je suis en avance c'est tout, avec la dernière génération de processeur mon soft est super rapide!", ce temps là est fini, révolu.

Aujourd'hui un soft lent sur un dual core sera peut être même encore plus lent sur un quad core d'une autre série (il y a plein de séries de processeurs on ne s'y retrouve d'ailleurs plus pour choisir...).

Donc, pour qu'un soft soit plus rapide, inutile de compter sur la prochaine génération de processeur qui doublera la fréquence d'horloge. Aujourd'hui on double le nombre de coeurs, c'est tout. un, puis deux, les quatre coeurs se banalisent, Intel avait annoncé (le tiendront ils) que d'ici peu de temps on atteindrait les 100 coeurs... Mais pour tirer partie de tous ces coeurs finalement pas si rapide, une seule solution le parallélisme !

Parallèlisme et programmation

Seulement voilà, pour bénéficier de tous ces coeurs il faut programmer selon un mode parallèle. Le plus "simple" consiste à faire du multi-threading en laissant l'OS répartir les threads entre les coeurs, mais pour plein de raison (je vous laisse y réfléchir) cela n'est pas forcément optimal. Un simple exemple : une tâche un peu lourde, vous n'avez pas besoin de 50 threads, vous avez une grosse bonne boucle qui traite une image pour faire du morphing par exemple. Allez programmez ça en créant plusieurs threads ! Si vous y arriver sans y engloutir des semaines de mise au point, je veux bien manger mon chapeau (il faudra fournir le chapeau je n'en porte pas, et si vous le fournissez, ça ne sera pas le mien, donc je ne le mangerais pas. On ne m'a pas si facilement...) !

Car en effet, multiplier les threads n'est pas toujours réalisable, en tout cas simplement. Découper une tâche en sous tâches parallélisables est en réalité tellement complexe, surtout de façon générique, que les langages et les OS sont bien à la traîne par rapport aux microprocesseurs. A la traîne ? Pas tous... Car sous .NET il va falloir compter avec PLINQ et PCP !

PLINQ et PCP

En très gros il s'agit d'extensions du framework .NET qui permet de paralléliser à peu près tout traitement sans presque aucune programmation particulière. Oui vous avez bien lu...

Ainsi, PLINQ est une extension parallèle de LINQ. Une requête LINQ comme :

var result = from c in liste where c.champ==5 orderby c.autreChamp select c; // version simple

devient en parallèle :

var result = from c in liste.AsParallel() where c.champ==5 orderby c.autreChamp select c; // parallélisée

Et c'est tout ! D'un seul coup la requête devient parallèle et s'executera sur tous les coeurs disponibles à la fois, le boulot étant découpé, les threads créés, et tout le reste, de façon automatique par PLINQ. Un rêve ? Non, pas besoin de se pincer, c'est déjà en preview à télécharger ! (voir en fin de billet).

Bien entendu il existe beaucoup de variantes et une syntaxe appropriée pour, par exemple, modifier le nombre de coeurs utilisés et plein d'autres nuances subtiles.

La parallélisation de LINQ est une pure merveille, mais il y a aussi PCP qui est le framework plus généraliste permettant de paralléliser des foreach, des for, etc, juste avec un mot clé. Il existe là aussi toute une panoplie de méthodes qu'il va falloir découvrir. Mais vous avouerez que le bond en avant est aussi fantastique que celui de LINQ par exemple. Je ne pense sincèrement pas que quiconque ayant utilisé LINQ ne voudra dans l'avenir d'un langage ne supportant pas une telle extension. Il y a eu un avant LINQ, il y aura forcément un après LINQ, mais on ne traversa pas le temps sans faire référence à cette nouvelle ère, au sens premier, le clou d'airain planté par le prêtre dans la mur du temple de la Rome antique pour marquer un événement crucial d'une telle importance qu'une nouvelle série d'années commençait.

PLINQ et PCP marqueront de la même façon une nouvelle ère.

Faire joujou ?

Je reconnais bien le geek qui sommeille en vous ! Oui c'est possible ! Il existe une CTP sortie en juin et qui est téléchargeable sur le site MSDN consacré à la programmation parallèle. Vous y trouverez de nombreuses informations qui complèteront facilement la très rapide présentation faite ici.

Le site : http://msdn.microsoft.com/fr-fr/concurrency/

... Vous êtes encore là ? Mais foncez je vous dit, c'est l'éclate du geek ce truc ! (mais pensez à revenir hein.. Stay Tuned !) Wink

[EDIT: lire aussi ce billet plus récent qui donne l'adresse d'un article sur PFX et de plusieurs Webcasts]

Astuce : reformater une chaîne sur plusieurs lignes (comme une requête SQL par exemple)

Il arrive souvent qu'on ait besoin de faire un copier-coller d'une chaine de caractères fractionnée en plusieurs lignes. Qu'il s'agisse d'un bout de texte avec des retours à la ligne ou bien par exemple une requête SQL, s'il y a beaucoup de lignes la mise en forme est fastidieuse. Il faut indenter chaque ligne, mettre des guillemets au début et à la fin, ajouter un "+" à chaque ligne... C'est vraiment le genre de chose qui coupe l'élan en plein milieu du codage d'une méthode.

Mais il existe une solution simple, très simple même, qui fait intervenir le chercher/remplacer de Visual Studio avec deux mini expressions régulières, tellement petites que même si vous êtes réfractaire aux ER je suis sûr que celles là vous les noterez et les utilisez !

Acte 1 : une belle requête

Supposons une requête ni trop petite ni trop grosse, pour notre exemple, par exemple générée par un outil comme MKQB ou un autre VQB :

SELECT
  dbo.INPUTUNIT.BATCH_ID,
  dbo.INPUTUNIT.FOLD_ID,
  dbo.INPUTUNIT.DOCUMENT_ID,
  dbo.INPUTUNIT.SHEET_ID,
  dbo.INPUTUNIT.PAGE_ID,
  dbo.INPUTUNIT.ENTITYID,
  dbo.INPUTUNIT.CREATEDON,
  dbo.INPUTUNIT.CREATEDBY,
  dbo.INPUTUNIT.MODIFIEDON,
  dbo.INPUTUNIT.MODIFIEDBY,
  dbo.INPUTUNIT.LASTERROR,
  dbo.INPUTUNIT.NAME,
  dbo.INPUTUNIT.TYPE,
  dbo.INPUTUNIT.IMAGEID,
  dbo.INPUTUNIT.STATE,
  dbo.INPUTUNIT.REASON,
  dbo.INPUTUNIT.EXPIRYDATE,
  dbo.INPUTUNIT.POSITION,
  dbo.INPUTUNIT.PRIORITY,
  dbo.INPUTUNIT.COMMINGFROMSTEP,
  dbo.INPUTUNIT.NEXTWORKFLOWSTEP,
  dbo.INPUTUNIT.CURRENTVALUE,
  dbo.INPUTUNIT.REGEXPRESSION,
  dbo.INPUTUNIT.NULLALLOWED,
  dbo.INPUTUNIT.CHECKMINMAX,
  dbo.INPUTUNIT.MINVALUE,
  dbo.INPUTUNIT.MAXVALUE,
  dbo.INPUTUNIT.VALUELIST,
  dbo.INPUTUNIT.MULTIVALUE,
  dbo.INPUTUNIT.STRICTVALUELIST,
  dbo.INPUTUNIT.CROPZONE,
  dbo.INPUTUNIT.INPUTCOUNT,
  dbo.INPUTUNIT.CONFIDENCEINDEX,
  dbo.INPUTUNIT.FORMAT,
  dbo.INPUTUNIT.DATATYPE,
  dbo.INPUTUNIT.DECIMALDIGITS,
  dbo.INPUTUNIT.MONEYFACTOR,
  dbo.INPUTUNIT.CASING,
  dbo.INPUTUNIT.CHECKLENGTH,
  dbo.INPUTUNIT.MAXLENGTH,
  dbo.INPUTUNIT.MINLENGTH,
  dbo.INPUTUNIT.MINSECURITYLEVEL,
  dbo.INPUTUNIT.CHECKDLL,
  dbo.INPUTUNIT.DLLCHECKNUMBER,
  dbo.INPUTUNIT.COMMENT
FROM
  dbo.INPUTUNIT
  INNER JOIN dbo.FOLD ON (dbo.INPUTUNIT.FOLD_ID = dbo.FOLD.ENTITYID)
  INNER JOIN dbo.BATCH ON (dbo.INPUTUNIT.BATCH_ID = dbo.BATCH.ENTITYID)
WHERE
  (dbo.FOLD.FOLDTYPE = 0) AND
  (dbo.BATCH.CUSTOMERID LIKE '%') AND
  (dbo.BATCH.BUSINESSID LIKE '%') AND
  ((dbo.INPUTUNIT.STATE = 5) OR
  (dbo.INPUTUNIT.STATE = 6))
 

C'est une requête somme toute très classique (et réelle, prise au hasard d'une appli en cours). Mais voilà, elle est sur "plein de lignes", elle est indentée avec des espaces au début, il va falloir jongler au clavier pour supprimer les espaces, placer les guillemets au début et à la fin et un petit signe + pour finir...
Nous commençons par coller cette requête dans le corps d'une méthode. je vous fait grace de la capture écran, on trouve une méthode "machin" ses accolades de début et de fin et ... notre belle requête en plein milieu comme un cheveu sur la soupe de C#.

Acte 2 : Désindenter

Pour ça, c'est facile, mais peut-être ne connaissez-vous l'astuce : sélectionnez tout le bloc texte en question, et tapez autant de fois que nécessaire la combinaison SHIFT-TAB jusqu'à ce que toutes les lignes se retrouvent collées à gauche. C'est un bon début et c'est rapide à faire.

Acte 3 : Chercher - Remplacer

Le plus gros de l'effort a été fait, et nous n'avons pas fait grand chose pour le moment...
Vérifiez que le texte est toujours sélectionné puis ouvrez la boîte de dialogue Chercher/Remplacer (CTRL-H) et renseignez les champs comme le montre cette capture écran :

 

Le texte à chercher est {.+} et le texte de remplacement est +"\1 ", cochez "Use " puis "Regular expression" (Utiliser/Expressions régulières pour la version FR). Puis cliquez sur "Replace All" (Remplacer tout). Vous noterez l'espace après le "1", c'est au choix, il ajoute un espace à chaque ligne avant de mettre le guillement fermant, pour une requête SQL c'est putôt préférable, dans d'autres circonstances cela peut gêner ou bien être remplacé par un autre symbole.

Et voici le résultat (du à la mise en page la requête est indentée sous la méthode, en réalité elle reste collée à gauche) :

static void Main(string[] args)
{
+
"SELECT "
+
"dbo.INPUTUNIT.BATCH_ID, "
+
"dbo.INPUTUNIT.FOLD_ID, "
+
"dbo.INPUTUNIT.DOCUMENT_ID, "
+
"dbo.INPUTUNIT.SHEET_ID, "
+
"dbo.INPUTUNIT.PAGE_ID, "
+
"dbo.INPUTUNIT.ENTITYID, "
+
"dbo.INPUTUNIT.CREATEDON, "
+
"dbo.INPUTUNIT.CREATEDBY, "
+
"dbo.INPUTUNIT.MODIFIEDON, "
+
"dbo.INPUTUNIT.MODIFIEDBY, "
+
"dbo.INPUTUNIT.LASTERROR, "
+
"dbo.INPUTUNIT.NAME, "
+
"dbo.INPUTUNIT.TYPE, "
+
"dbo.INPUTUNIT.IMAGEID, "
+
"dbo.INPUTUNIT.STATE, "
+
"dbo.INPUTUNIT.REASON, "
+
"dbo.INPUTUNIT.EXPIRYDATE, "
+
"dbo.INPUTUNIT.POSITION, "
+
"dbo.INPUTUNIT.PRIORITY, "
+
"dbo.INPUTUNIT.COMMINGFROMSTEP, "
+
"dbo.INPUTUNIT.NEXTWORKFLOWSTEP, "
+
"dbo.INPUTUNIT.CURRENTVALUE, "
+
"dbo.INPUTUNIT.REGEXPRESSION, "
+
"dbo.INPUTUNIT.NULLALLOWED, "
+
"dbo.INPUTUNIT.CHECKMINMAX, "
+
"dbo.INPUTUNIT.MINVALUE, "
+
"dbo.INPUTUNIT.MAXVALUE, "
+
"dbo.INPUTUNIT.VALUELIST, "
+
"dbo.INPUTUNIT.MULTIVALUE, "
+
"dbo.INPUTUNIT.STRICTVALUELIST, "
+
"dbo.INPUTUNIT.CROPZONE, "
+
"dbo.INPUTUNIT.INPUTCOUNT, "
+
"dbo.INPUTUNIT.CONFIDENCEINDEX, "
+
"dbo.INPUTUNIT.FORMAT, "
+
"dbo.INPUTUNIT.DATATYPE, "
+
"dbo.INPUTUNIT.DECIMALDIGITS, "
+
"dbo.INPUTUNIT.MONEYFACTOR, "
+
"dbo.INPUTUNIT.CASING, "
+
"dbo.INPUTUNIT.CHECKLENGTH, "
+
"dbo.INPUTUNIT.MAXLENGTH, "
+
"dbo.INPUTUNIT.MINLENGTH, "
+
"dbo.INPUTUNIT.MINSECURITYLEVEL, "
+
"dbo.INPUTUNIT.CHECKDLL, "
+
"dbo.INPUTUNIT.DLLCHECKNUMBER, "
+
"dbo.INPUTUNIT.COMMENT "
+
"FROM "
+
"dbo.INPUTUNIT "
+
"INNER JOIN dbo.FOLD ON (dbo.INPUTUNIT.FOLD_ID = dbo.FOLD.ENTITYID) "
+
"INNER JOIN dbo.BATCH ON (dbo.INPUTUNIT.BATCH_ID = dbo.BATCH.ENTITYID) "
+
"WHERE "
+
"(dbo.FOLD.FOLDTYPE = 0) AND "
+
"(dbo.BATCH.CUSTOMERID LIKE '%') AND "
+
"(dbo.BATCH.BUSINESSID LIKE '%') AND "
+
"((dbo.INPUTUNIT.STATE = 5) OR "
+
"(dbo.INPUTUNIT.STATE = 6)) "
}

Il n'y a plus qu'à se placer au début de la chaîne et de taper "var machaine = " puis de se placer en fin de la dernière ligne de la chaîne, de taper ";" et hop! c'est tout fini tout beau, remis en page et sans risque d'oubli ou de fausse manip ! Là aussi je vous fait grace de la capture écran, vous imaginez facilement le résultat, et puis, profitez-en pour tout de suite refaire la manip histoire de la mémoriser !

Conclusion

Une astuce simple qui rend un grand service et une preuve de plus que pour rébarbatives et contre-intuitives que soient les expressions régulières, savoir s'en servir est toujours un gros avantage...

Bon dev.. et Stay Tuned !

 

Les class helper : s'en servir pour gérer l'invocation des composants GUI en multithread

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 !)

Votre compte Gmail est-il piraté ?

Gmail, paradis et enfer tout à la fois. Paradis de disposer d'un compte mail ubiquitaire disponible de partout tout le temps, de bénéficier d'archives totales sur des années. Enfer du courrier privé et confidentiel accessible depuis partout avec un simple login, enfer de violation de la vie privée lorsque Gmail lui-même lit vos mails pour mieux cibler la pub qu'il affiche à côté pendant que vous les consulter... Mais le risque le plus grand est bien entendu que quelqu'un d'autre lise vos mails.

Avec un système de type Outlook, pas de problème, tout est sans cesse téléchargé sur votre PC et toute indiscrétion suppose que l'indélicat puisse accéder à votre machine en votre absence. A vous de mettre des mots de passe qui la protège et l'affaire est jouée. Mais avec Gmail ?

Histoire certainement de nous faire oublier qu'ils violent eux-mêmes notre intimité en lisant nos mails pour afficher des pubs ciblées, Gmail vient de mettre en place un système permettant de vérifier les accès à son compte.

Regardez tout en bas de l'écran, sous l'indication de votre utilisation de l'espace disque :

En cliquant sur "Details" vous obtiendez un écran vous permettant de voir les IP des derniers accès avec l'heure mais vuos pourrez aussi couper immédiatement l'accès à toutes les IP différentes de celle de la machine sur laquelle vous êtes. Utile au bureau si on a laissé sa page Gmail ouverte sur une autre machine 2 étages plus haut, pratique pour vérifier que personne n'a piraté votre compte (consultation depuis des hot spots publiques par exemple), etc.

Voici la capture de l'écran de détail :

 

Amis paranos, bonne journée !

Et.. Stay Tuned !

Comment passer outre la limitation du DISTINCT sur un SELECT contenant un champ text sous SQL Server

Voici un billet moins "hi-tech" que d'habitude. Point de LINQ dans tout çà, juste un bon server SQL SERVER, une table contenant un champ Text ou NText et une bête requête qui peut retourner plusieurs fois le même enregistrement. Et comme on ne désire pas voir les copies éventuelles, bien entendu on place instinctivement une clause DISTINCT dans le SELECT.

C'est beau, simple comme SQL... Sauf que... SQL Server n'aime pas du tout la clause DISTINCT s'il y a un champ texte dans le SELECT. Et pour cause, il ne sait pas comment comparer les contenus, du coup point de DISTINCT possible.

Quelques bonnes âmes vous conseilleront peut-être :

a) de changer tous vos champs texte en varchar
b) de vous passer du champ texte dans le SELECT et de l'obtenir à part dans une autre requête

Les conseilleurs ne sont pas les codeurs ! Les varchar ont une limite de 8000 caractères et il n'est pas toujours possible de les substituer à Text/NText. De plus modifier tous les champs de ce type dans une application peut être un énorme travail (code, procédures stockées, vues, tout cela à mettre à jour). Solution bidon donc.

Quant à faire le SELECT DISTINCT sans les champs texte puis à faire une seconde requêtre derrière pour les obtenir, c'est franchement lourd et pas forcément sans conséquence sur le code appelant (qui doit faire autant de sous requêtes que de records pour obtenir les champs texte, et les stocker, etc).

Non, il existe plus simple, et un peu plus vicieux : faire une jointure de la table sur elle même.

Oui, tout simplement. L'astuce consiste à faire un select dans la table de tous les champs (y compris les champs texte) mais sans la clause DISTINCT, table qu'on lie à elle même mais ce coup ci en faisant un SELECT DISTINCT qui lui omet les champs texte... La feinte est bonne, mais elle ne saurait faire de miracle, en effet, les champs texte ne seront pas utilisés pour faire la DISCTINCTion entre les records. Cette solution n'a donc qu'un seul hic : elle ne peut pas fonctionner si vous voulez vraiment que le DISTINCT prenne en compte les champs texte. Là je n'ai pas vraiment d'astuce à vous proposer. Mais pour les autres, voici un bout de code SQL fictif qui vous montrera la syntaxe à utiliser :

SELECT t1.c1, t1.c2, t1.LeChampTexte
FROM MaTable t1 JOIN
     (SELECT DISTINCT c1,c2 FROM MaTable WHERE Desc LIKE 'Test%') AS t2
     ON t1.c1 = t2.c1

on suppose ici que le champ "c1" représente la clé (il peut s'agir de plusieurs champs, of course, à vous d'adapter le code), que le champ "c2" est un autre champ sur lequel le DISTINCT portera et que "LeChampTexte" ... est le champ texte qui pose problème. On voit mieux ici que le DISTINCT ne sera effectué que sur "c1" et "c2". Mais cela permet bien de faire un DISTINCT tout en retournant les champs texte, le tout en une seule requête...

Bon SQL et... Stay Tuned !

Un cadeau à télécharger : des exemples LINQ !

Dernièrement je vous parlais des "101 exemples LINQ" publiés par Microsoft et je vous donnais les liens vers la page Web de ces derniers ainsi que vers un téléchargement d'une application complète distribuée avec la Ctp de mars 2007.

Hélas, qu'il s'agisse du site Web ou bien de l'appli en question, le code n'est pas tout à fait fonctionnel. On peut toujours le lire pour regarder les exemples de syntaxe LINQ mais il est quand même plus agréable de disposer d'une application qui marche pour tester.

A l'époque de la sortie des "101 exemples" j'avais commencé à me bricoler une application. En partant des exemples publiés sur le site Web j'avais construit une appli de test "laboratoire" pour expérimenter des tas de choses pas forcément liées à LINQ. Quand j'ai étudié plus tard l'exemple MS de la Ctp de mars 2007 je me suis aperçu que face aux mêmes problèmes le concepteur de cette dernière et moi avions finalement trouvé des solutions très proches. Et comme la version MS gérait deux ou trois choses que je n'avais pas implémentées j'ai décidé, en raison des ressemblances des codes, de les fusionner en une application simple et fonctionnelle.

Ce week-end le temps n'étant pas vraiment à la baignade (l'eau est bonne mais le vent de nord... qui interdit d'aller à la pêche aussi...) j'ai ressorti ce projet que j'avais laissé dans un coin de disque. je l'ai dépoussiéré et finalisé pour être utilisable. Il a profité de ce nettoyage d'hiver (ah bon ? c'est l'été ?) puisque cela a permis de le reprendre sous VS 2008 SP1 alors que ma version originale tournait sous la bêta. Au passage cela m'a permis de vérifier la bonne tenue de route de Resharper 4.0 qui gère vraiment bien les nouveautés de C# 3.0 et de LINQ (je vous en parlais aussi dernièrement).

Le code est téléchargeable ici (code source + l'exécutable dans bin/debug) : Linq101.rar (488,53 kb)

Une petite image de l'interface pour la route :

Bon téléchargement,

et ... Stay Tuned !

L'outil le plus intelligent enfin à jour ! (Resharper 4.0)

Resharper, si vous ne connaissez pas c'est que vous ne développez pas sous .NET ! C'est en effet un add'in incroyablement intelligent et indispensable une fois qu'on l'a essayé.

Il apporte à Visual Studio (depuis VS 2003) les petits plus qui transforment cet excellent IDE en une bombe proche de la perfection. Personnellement je ne peux plus m'en passer depuis longtemps.

Il y avait toutefois un bémol avec VS 2008, même si la version 3.1 était bien intégrée à l'IDE, elle ne savait pas gérer les dernières nouveautés du langage comme LINQ par exemple (oui encore LINQ...) ce qui gâchait le plaisir et obligeait à déconnecter bon nombre des features de Resharper lorsqu'on abordait un code plein de requêtes LINQ, d'expressions Lambda et autres derniers bijous syntaxiques.

Mais enfin tout est rentré dans l'ordre !

Avec un peu de retard sur la sortie de C# 3.0 (masi quel boulot le concepteur a du fournir !) la version 4.0 de Resharper est enfin disponible. Elle ajoute plein de nouvelles possibilités à cet outil déjà très complet mais, Ô comble de la satisfaction enfin retrouvée, elle gère parfaitement C# 3.0 et toutes ses nouvelles tournures, dont LINQ bien entendu.

A 199 tous petits dollars tous dévalués, pour la licence un développeur, c'est presque un cadeau et ça serait surtout une mesquinerie énorme que de ne pas se l'offrir (l'upgrade coûte encore moins cher si vous possédez une 3.0 et même une 2.0 pour VS 2003 me semble-t-il).

Pour ceux qui ne connaissent pas je ne vais pas détailler tout ce que Resharper fait déjà ni même les nouveautés de la 4.0, c'est trop long. Le mieux c'est que vous alliez directement sur le site de JetBrains et que vous regardiez de plus près les démos (fixes ou captures d'écrans animées flash).

N'oubliez pas aussi que Resharper supporte des plugins comme AgentSmith qui offre des contrôles supplémentaires comme par exemple la correction orthographique ou le contrôle des conventions de nommage (AgentSmith est gratuit, ce qui ne gâche rien).

Bon dev ...

... Et Stay Tuned !

Les 101 exemples de LINQ à portée de clic !

LINQ.. Je vous en parle très souvent car cela en vaut largement la peine. L'essayer c'est l'adopter, surtout que ses diverses moutures (Linq to XML, Linq to SQL, Linq to Object...) peuvent trouver leur place et rendre service dans tout type de développement.

Articles, astuces, j'ai beaucoup écrit sur LINQ et ce n'est certainement pas terminé. Mais il ne faudrait pas oublier les "101 exemples pour LINQ" créés par Microsoft.

A ma connaissance il n'existe pas de traduction, cela n'existe qu'en anglais. Mais c'est une belle visite de la syntaxe de LINQ et certainement un complément indispensable à une autoformation sur le sujet.

Ces exemples ne sont pas très récents mais je me suis rendu compte que peu de développeurs les connaissaient, au moins en France. D'où l'intérêt de rappeler leur existence.

Pour la petite histoire, le chef de projet qui a écrit ces exemples a touché $202 de la part de Anders... En effet, lorsqu'il a fallu écrire les exemples, Anders était assez pressé de voir le travail fini et avait promis $2 pour chaque exemple qui serait terminé à la dead-line, une sorte de plaisenterie. Le jour fatidique, arrivé en réunion, le chef de projet en avait réalisé 101. Anders a alors fouillé ses poches et a fait l'appoint, 101x2 = $202 qu'il a remis au gars un peu gêné, ne sachant s'il devait accepter ou pas... Ce bon chef de projet a utilisé la somme pour inviter le soir même les développeurs de son équipe au resto. Une belle histoire non ? :-)

PS: Vous pouvez aussi télécharger d'autres exemples Linq en cliquant ici [edit: en novembre 2012 le site pointé n'existe plus, sorry]. Ce n'est pas très récent (Ctp de mars 2007) mais pour ceux qui prennent Linq au vol plutôt qu'à ses débuts, il est fort probable, si vous êtes dans ce cas, que vous soyez passé à côté de ce téléchargement...

PS2: Si vous voulez faire tourner le code des 101 exemples LINQ, vous trouverez sur le site pointé par le lien donné en début d'article le code de la méthode GetProductList(). Ce code est assez exotique et ne compile pas. Il faut le modifier pour en tirer quelque chose. Pour vous simplifier la vous trouverez ci-après une unité de code C# contenant une définition correcte de cette fonction, encapsulée dans une classe abstraite. Cela devrait vous aider... Data.rar (2,26 kb)

Lé fotes d'ortografe ds les programs é lé page web c mauche !

Les fôtes d'ortografes dans les blogs, c courant, avec les sms les jeunes ne sav + écrir c connu.

Mouais... mais dans les applications c'est la tehon, disons-le franchement !

Et qui, ici, perd son temps à faire du copier coller de toutes les chaînes de caractères de son code pour les tester sous Word et les corriger ? Hein ? qui ? .... j'en étais sûr.

Je plaide coupable aussi d'ailleurs. Et c'est vraiment pas une bonne chose, car comme tout le monde, même en relisant j'en laisse passer des fautes.. On ne peut se concentrer sur le code qui implique logique et rigueur d'esprit et en même temps sur l'orthographe et la grammaire de notre langue, totalement illogique et sans rigueur d'esprit, je suis bien d'accord, je ne vous le fait pas dire. Mais quand même...

L'orthographe est sans nul doute la science des ânes, c'est la sagesse populaire qui le dit, mais les fautes dans les écrans des applications, qu'il s'agisse d'applis de bureau ou pire de pages Internet visibles par la planète bizarrement, et paradoxalement, ça ne vous fait pas passer non plus pour le plus brillant des intellectuels. C'est injuste je sais, mais c'est comme ça.

Il faut donc trouver une solution, simple et efficace qui ne réclame surtout pas trop de boulot à mettre en oeuvre, l'informaticien est fainéant, c'est connu (en tout cas les meilleurs, c'est presque un critère de sélection :-) ).

Si cette solution est gratuite c'est encore mieux (l'informaticien aime ce qui est gratuit, c'est aussi connu). Alors jetez un oeil sur IntelliSpell, un outil de Component One qui existe en version "community" gratuite. C'est ici, en cliquant ce lien. Un bémol tout de même, la version gratuite semble ne fonctionner qu'avec le dictionnaire US, quelle que soit la locale de votre Windows ou de VS 2008. J'ai demandé des précisions à Component One sur le sujet, mais visiblement ils ne sont pas pressés de répondre aux questions. Dommage. Mais l'outil reste intéressant à tester malgré tout.

Il existe bien entendu d'autres "spell checkers" pour Visual Studio, nommons sans ordre de préférence, à vous de voir :

  • StudioSpell
  • Arkhipov  [edit: en novembre 2012 le site ne propose plus ce lien, sorry]
  • Agent Smith (plugin gratuit pour Resharper)
  • et bien d'autres...

Si vous en connaissez un particulièrement bien, n'hésitez pas à l'indiquer en laissant un commentaire !

NB: ceux qui trouveront des fautes dans ce post auront gagné... le droit de se taire, on va pas jouer à ce jeu là hein, ça serait terriblement mesquin...Wink

Stay Tuned !