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 !

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

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 !

Quizz C#. Vous croyez connaître le langage ? et bien regardez ce qui suit !

C# est un langage merveilleux, plein de charme... et de surprises !

En effet, s'écartant des chemins battus et intégrant de nombreuses extensions comme Linq aujourd'hui ou les méthodes anonymes hier, il est en perpétuelle évolution. Mais ces "extensions" transforment progressivement un langage déjà subtile à la base (plus qu'il n'y paraît) en une jungle où le chemin le plus court n'est pas celui que les explorateurs que nous sommes auraient envisagé...

Pour se le prouver, 6 quizz qui vous empêcheront de bronzer idiot ou de vous endormir au boulot !

Un petit projet exemple avec les questions et les réponses sera téléchargeable en fin d'article demain...
[EDIT 4-7-08] Le lien se trouve désormais en fin d'article. Le code source contient les réponses au quizz.

Quizz 1

Etant données les déclarations suivantes :

class ClasseDeBase
{
   
public virtual void FaitUnTruc(int x)
    {
Console.WriteLine("Base.FaitUnTruc(int)"); }
}

class ClasseDérivée : ClasseDeBase
{
   
public override void FaitUnTruc(int x)
   { Console.WriteLine("Dérivée.FaitUnTruc(int)"); }

   public void FaitUnTruc(object o)
  {
Console.WriteLine("Dérivée.FaitUnTruc(object)"); }
}

Pouvez-vous prédire l'affiche du code suivant et expliquer la sortie réelle :

    ClasseDérivée d = new ClasseDérivée();
    int i = 10;
    d.FaitUnTruc(i);

Gratt' Gratt' Gratt'.....

Quizz 2

Pouvez-vous prédire l'affichage de cette séquence et expliquer l'affichage réel ?

double d1a = 1.00001;
double d2a = 0.00001;
Console.WriteLine((d1a - d2a) == 1.0);

double d1b = 1.000001;
double d2b = 0.000001;
Console.WriteLine((d1b - d2b) == 1.0);

double d1c = 1.0000001;
double d2c = 0.0000001;
Console.WriteLine((d1c - d2c) == 1.0);

Je vous laisse réfléchir ...

Quizz 3

Toujours le même jeu : prédire ce qui va être affiché et expliquer ce qui est affiché réellement... c'est forcément pas la même chose sinon le quizz n'existerait pas :-)

List<Travail> travaux = new List<Travail>();
Console.WriteLine("Init de la liste de delegates");
for (int i = 0; i < 10; i++)
{ travaux.Add(
delegate { Console.WriteLine(i); }); }

Console.WriteLine("Activation de chaque delegate de la liste");

foreach (Travail travail in travaux) travail();

 Les apparences sont trompeuses, méfiez-vous !

Quizz 4

Ce code compile-t-il ?

public enum EtatMoteur { Marche, Arrêt }

public static void DoQuizz()
{
   
EtatMoteur etat = 0.0;
   
Console.WriteLine(etat);
}

Quizz 5

 Etant données les déclarations suivantes :

private static void Affiche(object o)
{
Console.WriteLine("affichage <object>"); }

private static void Affiche<T>(params T[] items)
{
Console.WriteLine("Affichage de <params T[]>"); }

 Pouvez-vous prédire et expliquer la sortie de l'appel suivant :

  Affiche("Qui va m'afficher ?");

 Je sens que ça chauffe :-)

Quizz 6

 Etant données les déclarations suivantes :

 delegate void FaitLeBoulot(); private static FaitLeBoulot FabriqueLeDélégué()
{
  
Random r = new Random();
  
Console.WriteLine("Fabrique. r = "+r.GetHashCode());

  
return delegate {
                           
Console.WriteLine(r.Next());
                           
Console.WriteLine("delegate. r = "+r.GetHashCode());
                         };

}

 Quelle sera la sortie de la séquence suivante :

    FaitLeBoulot action = FabriqueLeDélégué();
    action();
   
action();

 

Conclusion

C# est un langage d'une grande souplesse et d'une grande richesse. Peut-être qu'à devenir trop subtile il peut en devenir dangereux, comme C++ était dangereux car trop permissif.

Avec C#, même de très bons développeurs peuvent restés interloqués par la sortie réelle d'une séquence qui n'a pourtant pas l'air si compliquée.
Ses avantages sont tels qu'il mérite l'effort de compréhension supplémentaire pour le maîtriser réellement, mais tout développeur C# (les moyens comme ceux qui pensent être très bons!) doit être mis au moins une fois dans sa vie face à un tel quizz afin de lui faire toucher la faiblesse de son savoir et les risques qu'il prend à coder sans vraiment connaître le langage.

Comme toujours l'intelligence est ce bel outil qui nous permet de mesurer à quel point nous ne savons rien.
Nous sommes plutôt habitués à envisager cette question sous un angle métaphysique, le développement nous surprend lorsque lui aussi nous place face à cette réalité...

Pour le projet exemple dont le source contient les réponses cliquez sur le lien en fin de billet.

So (et plus que jamais!) .. Stay Tuned !

Quizz.rar (103,41 kb)

Linq au quotidien, petites astuces et utilisations détournées...

Linq je vous en parle souvent, c'est tellement génial. Et ce que j'aime bien c'est trouver des utilisations presque à contre-sens de Linq.
J'entends par là des façons de "rentabiliser" Linq à toute autre tâche que d'interroger des données (SQL, XML ou autres).

Je ne suis pas le seul dans ce cas ! J'ai au moins trouvé un autre zigoto qui s'amuse au même jeu, Igor Ostrovsky dont vous pouvez visiter le blog (cliquez sur son nom). Comme il s'exprime en anglais et que je sais que certains visiteurs ici ne sont pas forcément doués pour cette langue, voici une adaptation libre(*) de son billet présentant quelques astuces très amusantes.
(*) je n'ai repris que les exemples de code, le texte et le projet exemple sont de mon cru, si vous parlez anglais je vous laisse découvrir la prose de Igor sur son blog.

Initialiser un tableau, une liste

Il est souvent nécessaire d'initialiser une array avec des éléments qui, pour que cela soit utile, répondent à certains critères. Normalement cela se règle soit sous la forme d'une liste de constantes dans la déclaration de l'array, ce qui est pour le moins assez peu souple, soit sous la forme d'un code d'initialisation, ce qui du coup devient assez lourd pour parfois pas grand chose... C'est oublier que Linq peut apporter ici une aide non négligeable !

Voici trois exemples :

int[] A = Enumerable.Repeat(-1, 10).ToArray();
int[] B = Enumerable.Range(0, 10).ToArray();
int[] C = Enumerable.Range(0, 10).Select(i => 100 + 10 * i).ToArray();

Les trois arrays contiendront 10 éléments. "A" contiendra 10 éléments tous égaux à -1. "B" contiendra la suite 0 à 9, et "C" contiendra la suite de 100 à 190 par pas de 10.

Amusant, n'est-il pas ?

Plusieurs itérations en une seule

Imaginons que nous possédions deux arrays, A1 et B2, chacune contenant une sélection différente d'éléments. Supposons que souhaitions appliquer le traitement "Calcule(x)" aux éléments de ces deux arrays. La solution s'imposant serait :

foreach (var x in A1) { Calcule(x); }
foreach (var x in A2) { Calcule(x); }

Ce n'est pas très long, mais répéter du code n'est jamais très élégant. Voici comme Linq permet de faire la même chose en une seule boucle :

foreach (var x in A1.Concat(A2)) { Calcule(x); }

L'opérateur Concat effectue une .. concaténation, donnant l'illusion à notre boucle qu'elle travaille sur une seule et même array alors qu'il n'en est rien... On notera que Linq travaillant au niveau des énumérateurs, il n'y même pas de variable temporaire créée par le mécanisme pour stocker la concaténation. C'est élégant et en plus c'est efficace du point de vue de la mémoire.

Fun, isn't it ?

Créer des séquences aléatoires

Il est parfois nécessaire de créer des séquences aléatoires, pour tester un bout de code par exemple. La classe Random est bien pratique mais encore une fois il faudra déclarer le tableau puis déclarer une méthode pour l'initialiser en utilisant Random. Avec Linq tout cela peut se réduire à deux déclarations :

Random rand = new Random();
var ListeAléatoire = Enumerable.Repeat(0, 10).Select(x => rand.Next(10,500));

Le tableau "ListeAléatoire" contiendra 10 nombres aléatoires entre 10 et 500, et pas une seule déclaration de méthode à l'horizon. Vous noterez en revanche l'utilisation qui est faite de l'expression Lambda (nouveauté de C# 3.0) au niveau du Select.

Créer des chaînes de caractères

Si les exemples portent jusqu'ici sur des tableaux de nombres, c'est par pure souci de simplification. Il est bien entendu possible d'appliquer les mêmes astuces à d'autres types de données comme les chaînes :

 string chaine = new string( Enumerable.Range(0, 20) .Select(i => (char)(‘A’ + i % 3)) .ToArray());

La chaîne "chaine" contiendra "ABCABCABCABCABCABCAB". Remplacez le "20" par un "rand.Next(10,100)" par exemple, et en plus la longueur de la chaîne sera elle-même aléatoire. C'est pas pratique ça ?

Dans les commentaires du blog de Igor, un lecteur proposait une autre façon d'initialiser une chaîne avec Linq, la voici pour l'intérêt de la démonstration de la versatilité de Linq :

string chaine2 = string.Join(string.Empty, Enumerable.Repeat("ABC", 7).ToArray());

Ici on peut passer une pattern et un nombre de répétition.

Convertir une valeur en séquence de 1 objet

Il est parfois nécessaire de convertir une valeur en une séquence de longueur 1. Passer un paramètre unique là où un tableau de valeur est attendu réclame de créer le fameux tableau et de l'initialiser. Rien de bien méchant mais pourquoi ne pas mettre Linq à contribution ?

IEnumerable<int> sequence = Enumerable.Repeat(LaValeur, 1);

La répétition de 1 élément peut sembler être une sorte de contresens, mais si les concepteurs de la fonction partageaient cette opinion n'auraient-ils pas levé une exception pour toute valeur inférieure à 2 ... :-)
C'est en tout cas un moyen bien pratique d'arriver au résultat souhaité en une seule opération !

Enumérer tous les sous-ensembles d'un tableau

Il s'agit plus ici d'un "plaisir pervers" que d'une réelle utilité quotidienne il faut l'avouer, mais pour l'intérêt de la syntaxe voici comment énumer tous les sous ensembles d'un tableau (attention, si la taille du tableau dépasse quelques dizaines d'éléments le temps de calcul peut vite exploser ...) :

T[] tableau = …;
var subsets = from m in Enumerable.Range(0, 1 << tableau.Length)
              select
                  from
i in Enumerable.Range(0, tableau.Length)
                  where (m & (1 << i)) != 0
                  select arr[i];

Je vous laisse réfléchir à la syntaxe, peu évidente de prime abord, et le côté un peu "tordu" de cette astuce, mais elle répond à un besoin qui l'est tout autant. Et Linq peut aussi servir à cela.
Quelques clés pour comprendre : la requête ne retourne pas des items de "tableau" mais bien des sous ensembles de "tableau", c'est à dire des tableaux. Chaque tableau contient une combinaison unique d'éléments de "tableau". Si vous prenez tableau = B de notre premier exemple (c'est à dire la suite d'entiers 0 à 9), la requete Linq ci-dessus retournera 1024 sous-ensembles. Avouez que bien qu'un peu difficile à comprendre à première lecture, le procédé est d'une grande élégance... (Vous pouvez regarder le fonctionnement de cette requête de plus près grâce au projet démo téléchargeable en fin d'article).

Un peu de code...

Pour mieux comprendre les choses il est toujours préférable de faire tourner un bout de code. J'y ai pensé ! Vous trouverez un projet console complet plus bas ;-)

Conclusion

Igor montre au travers de ces différentes utilisations de Linq ce que je tente aussi de vous montrer dans mes billets : Linq est d'une grande souplesse, Linq ne se limite pas à faire du SQL en C#, Linq est un atout majeur pour le développeur qui sait s'en servir au quotidien. Initialiser un tableau, convertir un tableau dans un type différent, jouer sur les combinatoires, fabriquer des chaînes de caractères ou des séquences aléatoires de test, etc, etc. Il n'y a pas un bout de code dans lequel il ne soit possible de glisser un peu de Linq pour le rendre plus sobre, plus élégant et souvent plus performant.
Remercions Igor pour ces exemples de code (seule chose reprise de son billet, j'ai mon propre style, il a le sien :-) )

Le code complet

LinqFun.rar (99,18 kb)

Bonus 

Mitsu, qu'on ne présente plus, ça serait faire insulte à sa notoriété !, a proposé il y a quelques temps déjà une autre astuce Linq tout à fait amusante, elle consiste à obtenir la décomposition d'un nombre en facteurs premiers au travers de requêtes Linq et d'un class helper permettant d'écrire : 144.ToPrimeNumbersProduct() et d'obtenir en sortie "2^3 * 3^2 * 7". Je vous en conseille la lecture en suivant ce lien.

 

Donner du peps à Linq ! (Linq dynamique et parser d'expressions)

J'ai eu moulte fois ici l'occasion de dire tout le bien que  je pense de LINQ, de sa puissance, de sa souplesse, de sa versatilité (de xml à SQL en passant par les objets et l'Entity Framework).

Mais une petite chose me chagrinait : toute cette belle puissance s'entendait "hard coded". Je veux dire par là que les expressions et requêtes LINQ s'écrivent en C# dans le code et qu'il semblait très difficile de rendre la chose très dynamique, sous entendu dépendant d'expressions tapées à l'exécution de l'application. En un mot, faire du LINQ dynamique comme on fait du SQL dynamique.

Ce besoin est bien réel et ne concerne pas que les sources SQL. Prenez une simple liste d'objets que l'utilisateur peut vouloir trier selon l'une des propriétés ou filtrer selon le contenu des objets... Comment implémenter une telle feature ?

Contourner ce manque en écrivant des parsers, en jonglant avec les expressions lamba et la réflexion n'est pas, il faut l'avouer, ce qu'il y a de plus simple. Mais rassurez-vous chez Microsoft des gens y ont pensé pour nous ! Seulement voila, la chose est assez confidentielle il faut bien le dire, et on ne tombe pas dessus par hasard. Ou alors c'était un jour à jouer au Lotto, voire à contrôler, selon les mauvaises langues, l'emploi du temps de sa femme !

La chose s'appelle "LINQ Dynamic Query Library", un grand nom pour une petite chose puisqu'il s'agit en réalité d'un simple fichier source C# à ajouter à ses applications.  Mais quel fichier source !

Toute la difficulté consiste donc à savoir que ce fichier existe et mieux, où il se cache... C'est là que c'est un peu vicieux, la chose se cache dans un sous-répertoire d'un zip d'exemples Linq/C#, fichier lui-même astucieusement planqué dans la jungle des milliers de téléchargement de MSDN...

Sans plus attendre allez chercher le fichier cliquant ici.

Une fois que vous aurez accepté les termes de la licence ("I Accept" tout en bas à gauche) vous recevrez le fichier "CSharpSamples.zip". Dans ce dernier (dont tout le contenu est vraiment intéressant) aller dans le répertoire "LinqSamples" puis dans le projet "DynamicQuery" vous descendrez dans une autre sous-répertoire appelé lui aussi "DynamicQuery" (non, ce n'est pas un jeu de rôle, juste un téléchargement MSDN!). Et là, le Graal s'y trouve, "Dynamic.cs".

Copiez-le dans vos projets, et ajouter un "using System.Linq.Dynamic". A partir de là vous pourrez utiliser la nouvelle syntaxe permettant de faire du LINQ dynamique. A noter que dans le répertoire de la solution vous trouverez aussi un fichier "Dynamic Expressions.html" qui explique la syntaxe du parser.

Quelles sont les possibilités de LINQ Dynamic Query Library ?

C'est assez simple, pour vous expliquer en deux images je reprend deux captures écrans tirées du blog de Scott Guthrie (un excellent blog à visiter si vous parlez l'anglais).

Voici un exemple de requête LINQ (en VB.NET, si j'avais refait la capture cela aurait été du C#, mais je ne vais pas rouspéter hein ! )

Maintenant voici la même requête utilisant du LINQ dynamique :

Vous voyez l'astuce ? Le filtrage et le tri sont maintenant passés en chaîne de caractères... Bien entendu cette chaîne peut avoir été construite dynamiquement par code (c'est tout l'intérêt) ou bien saisie depuis un TextBox rempli par l'utilisateur.

Ici pas de risque d'attaque par "SQL injection", d'abord parce que LINQ n'est pas limité à SQL et que le problème ne se pose que dans ce cas, mais surtout parce que LINQ to SQL utilise des classes issues d'un modèle de données "type safe" et que par défaut LINQ to SQL est protégé contre les attaques de ce type. Pas de souci à se faire donc.

Pour terminer j'insisterai lourdement sur le fait que LINQ ne se limite pas aux données SQL, et que l'astuce de LINQ dynamique ici décrite s'applique même à LINQ to Object par exemple. Laisser la possibilité à un utilisateur de trier une List<> ou une Collection<>, de la filtrer, tout cela en mémoire et sans qu'il y ait la moindre base de données est bien entendu possible.

LINQ est vraiment une innovation majeure en matière de programmation, pouvoir le dynamiser simplement c'est atteindre le nirvana...

Je vous laisse vous jeter sur vos claviers pour expérimenter du LINQ dynamique, amusez-vous bien !

..et surtout.. Stay tuned !

 

Le blues du "Set Of" de Delphi en C#

Il y a bien fort peu de chose qui puisse faire regretter un delphiste d'être passé à C#, et quand je parle de regrets, c'est un mot bien fort, disons plus modestement des manques agaçants. 

Rien qui puisse faire réellement pencher la balance au point de revenir à Delphi, non, ce langage est bel et bien mort, assassiné par Borland/Inprise/Borland/CodeGear et son dernier big boss, tod nielsen qui ne mérite pas même les majuscules à son nom mais là n'est pas la question.

Donc il existe syntaxiquement trois choses qui peuvent agacer le delphiste, même, comme moi, quand cela fait au moins cinq ans maintenant que je ne pratique presque plus que C# (et un peu de Delphi pour maintenir des vieilleries et aider certains clients à tourner la page en migrant sous C#).

La première qui revient à longueur de code, ce sont ces satanés parenthèses dans les "if". On n'arrête pas de revenir en arrière parce qu'on rajoute un test dans le "if" et qu'il faut remettre tout ça entre de nouvelles parenthèses qui repartent depuis le début. Certes on gagne l'économie du "then" pascalien, mais que ces parenthèses du "if" sont épouvantables et ralentissent la frappe bien plus qu'un "then" unique et ponctuel qu'on ne touche plus une fois écrit même si on rajoute "and machin=truc" ! A cela pas d'astuce ni truc. Et aucun espoir que les parenthèses de C# dans les "if" soient abandonnées un jour pour revenir au "then" ... Donc faut faire avec ! Le dogme "java/C++" est bien trop fort (quoi que C# possède le Goto de Delphi, ce qui n'est pas la meilleure idée du langage :-) ).

La seconde tracacerie syntaxique est cette limitation totalement déroutante des indexeurs : un seul par classe et il ne porte pas de nom. this[], un point c'est tout. Je sais pas ce que notre bon Hejlsberg avait en tête, mais pourquoi diable n'a-t-il repris de Delphi qu'un seul indexeur et non pas la feature entière ? Il fait beaucoup de recherche, et le fait bien, mais je présume qu'il n'a jamais plus codé un "vraie" appli depuis des lustres... Car dans la vraie vie, il existe plein de situations où un objet est composé de plus d'une collections. Une voiture a 4 roues et 2 ou 4 portières. Pourquoi lorsque je modélise la classe Voiture je devrais donner plus d'importance aux roues qu'aux portières et choisir lesquelles auront le droit d'être l'unique indexeur de la classe ? Pourquoi tout simplement ne pas avoir Voiture.Roues[xx] et Voiture.Portières[yy] ? Mon incompréhension de ce choix très gênant dans plus d'un cas a été et reste des années après totale. Surtout après toutes les évolutions de C# sans que jamais cette erreur de conception ne soit corrigée. Pas suffisant pour faire oublier toute la puissance de C# et le bonheur qu'on a à travailler dans ce langage, mais quand même, ça agace.

Enfin, dans la même veine d'ailleurs, l'absence de "Set of" est cruelle. Pourquoi avec zappé cette feature de Delphi dans C# alors que bien d'autres ont été reprises (avec raison ou moins comme le Goto) ?

Mais là on peut trouver des astuces et certains (dont je fais partie) ont écrit ou essayer d'écrire des classes "SetOf" qui permettent de gérer des ensembles comme Delphi, mais que c'est lourd tout ce code au lieu d'écrire "variable machin : set of typeTruc" !

Autre astuce moins connue, et c'est pour ça que je vous la livre, est l'utilisation fine des Enums. En effet, tout comme sous Delphi, il est possible d'affecter des valeurs entières à chaque entrée d'une énumération. On peut donc déclarer "enum toto { item1 = 5, item2 = 28, item3 = 77 }". 

Mais ce que l'on sait moins c'est que rien ne vous interdit d'utiliser des puissances de 2 explicitement car les Enums savent d'une part parser des chaînes séparées par des virgules et d'autre part savent reconnaître les valeurs entières cumulées.

Ainsi, avec enum Colors { rouge=1, vert=2, bleu=4, jaune=8 }; on peut déclarer :
Colors orange = (Colors)Enum.Parse(typeof(Colors),"rouge,jaune"); // étonnant non ?

La valeur de "orange" sera 9 qu'on pourra décomposer facilement, même par un simple Convert.ToInt64.

Pour ne pas réinventer la roue et éviter de faire des coquilles je reprends cet exemple tel que fourni dans la doc MS. Voici le code complet qui vous permettra, peut-être, d'oublier plus facilement "Set of" de Dephi...

Stay tuned!

Code de la doc MS :

using System;

public class ParseTest
{
    [FlagsAttribute]
    enum Colors { Red = 1, Green = 2, Blue = 4, Yellow = 8 };

    public static void Main()
    {
        Console.WriteLine("The entries of the Colors Enum are:");
        foreach (string colorName in Enum.GetNames(typeof(Colors)))
        {
            Console.WriteLine("{0}={1}", colorName,
                                         Convert.ToInt32(Enum.Parse(typeof(Colors), colorName)));
        }
        Console.WriteLine();
        Colors myOrange = (Colors)Enum.Parse(typeof(Colors), "Red, Yellow");
        Console.WriteLine("The myOrange value {1} has the combined entries of {0}",
                           myOrange, Convert.ToInt64(myOrange));
    }
}

/*
This code example produces the following results:

The entries of the Colors Enum are:
Red=1
Green=2
Blue=4
Yellow=8

The myOrange value 9 has the combined entries of Red, Yellow

*/

 

 

Les class Helpers, enfer ou paradis ?

Class Helpers 

Les class helpers sont une nouvelle feature du langage C# 3.0 (voir mon billet et mon article sur les nouveautés de C# 3.0).

Pour résumer il s'agit de "décorer" une classe existante avec de nouvelles méthodes sans modifier le code de cette classe, les méthodes en questions étant implémentées dans une autre classe.

Le principe lui-même n'est pas récent puisque Borland l'avait "inventé" pour Delphi 8.Net (la première version de Delphi sous .Net) afin de permettre l'ajout des méthodes de TObject à System.Object (qu'ils ne pouvaient pas modifier bien entendu) afin que la VCL puisse être facilement portée sous .Net. Borland avait déposé un brevet pour ce procédé (ils le prétendaient à l'époque en tout cas) et je m'étonne toujours que Microsoft ait pu implémenter exactement la même chose dans C# sans que cela ne fasse de vagues. Un mystère donc, mais là n'est pas la question.

Mauvaises raisons ?

Les deux seules implémentations de cet artifice syntaxique que je connaisse (je ne connais pas tout hélas, si vous en connaissez d'autres n'hésitez pas à le dire en commentaire que l'on puisse comparer) sont donc celle de Borland dans Delphi 8 pour simplifier le portage de la VCL sous .NET et celle de Microsoft dans C# pour faciliter l'intégration de Linq dans le langage.

Deux exemples, deux fois pour la même raison un peu spécieuse à mes yeux : simplifier le boulot du concepteur du langage pour supporter de nouvelles features. Deux fois une mauvaise raison à mes yeux, un peu trop puristes peut-être, qui pensent qu'un élément syntaxique doit se justifier d'une façon plus forte, plus théorique que simplement "pratique".

Résultat, je me suis toujours méfié des class helpers car leur danger est qu'un objet se trouve d'un seul coup affublé de méthodes "sorties d'un chapeau", c'est à dire qu'il semble exposer des méthodes publiques qui ne sont nulles part dans son code. J'ai horreur de ce genre de combines qui, à mon sens, favorise un code immaintenable. Si j'adore la magie, à voir ou à pratiquer, je la déteste lorsque je porte ma casquette d'informaticien... J'ai donc toujours conseillé la plus grande circonspection vis à vis de cet artifice syntaxique, que ce soit à l'époque (déjà lointaine.. le temp passe!) où je faisais du Delphi que maintenant sous C# qui vient d'ajouter cette fioriture à sa palette.

Jusqu'à aujourd'hui, faute d'avoir trouver une utilisation intelligente des class helpers qui ne puissent être mise en oeuvre plus "proprement", les class helpers étaient à mon sens plutôt à classer du côté enfer que paradis. Interface et héritage m'ont toujours semblé une solution préférable à ces méthodes fantomes.

Trouver une justification

Mais il n'y a que les imbéciles qui ne changent pas d'avis, n'est-ce pas... Et je cherche malgré tout toujours à trouver une utilité à un outil même si je le trouve inutile de prime abord, réflexe d'ingénieur qui aime trouver une place à toute chose certainement.
J'ai ainsi essayé plusieurs fois dans des projets de voir si les class helpers pouvaient rendre de vrais services avec une réelle justification, c'est à dire sans être un cache misère ni une façon paresseuse de modifier une classe sans la modifier tout en la modifiant...

Comme j'ai enfin trouvé quelques cas (rares certes) dans lesquels les class helpers me semblent avoir une justification pratique, je me suis dit que vous en toucher deux mots pourraient éventuellement faire avancer votre propre réflexion sur le sujet (même si j'ai bien conscience que je dois être assez seul à me torturer la cervelle pour trouver absolument une utilité aux class helpers :-) ).

Bref, passons à la pratique.

Un cas pratique

Premier cas, les chaînes de caractères. Voilà un type de données vieux comme la programmation qui pourrait être un bon candidat à l'exploitation des possibilités des class helpers. En effet, hors de question de dériver la classe System.String et encore moins de pouvoir modifier le mot clé "string" de C#. Pourtant nous utilisons très souvent les mêmes fonctions "personnelles" sur les chaînes de caractères d'un même projet.

Par exemple, j'ai pour principe que les chaînes exposées par une classe (propriétés de type string donc) ne soient jamais à null. De ce fait, dans toutes les classes qui exposent un string, j'ai, dans le setter, la même séquence qui change l'éventuel null de value à string.Empty. C'est assez casse-pieds à répéter comme ça mécaniquement dans toutes les propriétés string de toutes les classes.

Et là, pas de possibilité de faire supporter une interface à System.String, ni de la dériver comme je le disais plus haut. C'est ici que les class helpers peuvent trouver une première justification pratique pour le développeur en dehors d'avoir facilité la vie à MS pour implémenter Linq.

Prenons le code suivant que nous plaçons dans une unité "Tools" de notre projet :

public static class Utilities
{

     public static string NotNullString(this string s)
      { 
return !string.IsNullOrEmpty(s) ? s.Trim() : string.Empty; }
...
 }

La classe Utilities est une classe statique qui contient tous les petits bouts de code utilisables dans tout le projet. Parmi ces méthodes, on voit ici l'implémentation du class helper "NotNullString". D'après cette dernière, la méthode ne sera visible que sur les instances de la classe "string". Le code lui-même est d'une grande simplicité puisqu'il teste si la chaîne en question est vide ou non, et, selon le cas, retourne string.Empty ou bien un Trim() de la chaîne. J'aime bien faire aussi systématiquement un Trim() sur toutes les propriétés string, je trouve ça plus propre surtout si on doit tester des équalités et que les chaînes proviennent de saisies utilisateurs.

Dans la pratique il suffira maintenant n'importe où dans notre projet d'écrire la chose suivante pour être certain qu'à la sortie de l'affectation la chaîne résultante ne sera jamais nulle et qu'elle ne comportera jamais d'espaces en trop en début ou en fin :

public string BusinessID
{  
get { return _BusinessID; }
    
set if (value != _BusinessID)
            
{ _BusinessID = value.NotNullString().ToUpperInvariant();
                
DoChange("BusinessID");
            
}
         
}
}

On voit ici une propriété string "BusinessID" qui, dans son setter, utilise désormais la nouvelle méthode fantome de la classe string... En fin de séquence nous sommes certains que _BusinessID est soit vide, soit contient un chaîne sans espace de tête ou de queue (et qu'elle est en majuscules, en plus dans cet exemple).

Voici donc une première utilisation "intelligente" des class helpers, la décoration d'une classe du framework (ou d'une lib dont on n'a pas le code source) pour lui ajouter un comportement, éventuellement complexe, dont on a souvent l'utilité dans un projet donné.

On pourrait penser ainsi à une fonction "ProperCase" qui passe automatiquement la casse d'une chaîne en mode "nom de famille", c'est à dire première lettre de chaque mot en majuscule, le reste en minuscule, ou à bien d'autres traitements qu'on aurait besoin d'appliquer souvent à des chaînes dans un projet.

Encore un autre cas

Dans la même veine, une application doit souvent manipuler des données issues d'une base de données et les modifier (en plus d'en insérer de nouvelles). On le sait moins (mais on s'aperçoit vite quand cela bug!) que le framework .NET possède, pour les dates par exemple, ses propres mini et maxi qui ne sont pas compatibles à toutes les bases de données, notamment SQL Server. Si vous attribuer à une date la valeur DateTime.MinValue et que vous essayez d'insérer cette dernière dans un champ Date d'une base SQL Server vous obtiendrez une exception de la part du serveur : la date passée n'est pas dans la fourchette acceptée par le serveur.
Dommage... DateTime.MinValue est bien pratique...

On peut bien entendu fixer une constante dans son projet et l'utiliser en place et lieu de DateTime.MinValue. Voci un exemple pour MaxValue (le problème étant le même):

DateTime MaxiDate = new DateTime(3000, 1, 1, 0, 0, 0);

Il suffira donc d'utiliser MaxiDate à la place de DateTime.MaxValue. La date considérée comme "maxi" est ici arbitraire (comme on le ferait pour la date mini) et est choisie pour représenter une valeur acceptable à la fois pour l'application et pour la base de données. Ici on notera que je prépare le terrain pour le bug de l'an 3000. Moins stupide qu'un coboliste et le bug de l'an 2000, vous remarquerez que je me suis arrangé ne plus être joignable à la date du bug et que mes héritiers sont aussi à l'abris de poursuites judiciaires pour quelques siècles :-)

L'utilisation d'une constante n'a rien de "sale" ou de "moche", c'est un procédé classique en programmation, même la plus éthérée et la plus sophistiquée. Toutefois, Puisque DateTime existe, puisque DateTime est un type complexe (une classe) et non pas un simple emplacement mémoire (comme les types de base en Pascal par exemple), puisque cette classe expose déjà des méthodes, dont Min et MaxValue, il serait finalement plus "linéaire" et plus cohérent d'ajouter notre propre MaxValue à cette dernière en place et lieu d'une constante.

Encore un bon exemple d'utilisation des class helpers. Ici nous homogénéisons notre style de programmation en évitant le mélange entre méthodes de DateTime et utilisation d'une constante. De plus, en ajoutant une méthode spécifique à DateTime, celle-ci sera visible par Intellisense come membre de DateTime, ce qui ne serait pas le cas de la constante. 

Dans la même classe Utilities nous trouverons ainsi :

public static DateTime SQLMaxValue(this DateTime d)
{
return new DateTime(3000, 1, 1, 0, 0, 0); }

 Et nous voici avec la possibilité d'écrire : MaDate = DateTime.SQLMaxValue();

Conclusion

Enfer ou paradis ? Les class helpers, comme tout artifice syntaxique peuvent se ranger dans les deux catégories, ils ne sont qu'un outil à la disposition du développeur. Un simple marteau peut servir à bâtir une maison où vivre heureux ou bien à assassiner sauvagement son voisin...  Les objets inanimés n'ont pas de conscience, ou plutôt, si, ils en ont une : celle de celui qui les utilise. A chacun d'utiliser les outils que la technologie humaine met à sa disposition pour créer un enfer ou un paradis...

Techniquement, je n'enfoncerai pas les portes ouvertes en donnant l'impression d'avoir découvert une utilisation miraculeuse des class helpers. Cela serait stupide, puisque justement cette syntaxe a été créée par Borland puis MS pour justement décorer des classes dont on ne possède pas le source (pas d'ajout de méthode ni d'interface) et qui ne sont pas héritables. En ajoutant des class helpers à string ou DateTime nous ne faisons rien d'autre que d'utiliser les class helpers exactement en conformité avec ce pour quoi ils ont été créés.

L'intérêt de ce billet se situe alors dans deux objectifs : vous permettre de réfléchir à cette nouveauté de C#3.0 que vous ne connaissez peut-être pas ou mal et vous montrer comment, en pratique, cela peut rendre des services non négligeables.

Si l'un ou l'autre de ces objectifs a été atteint, vous tenez alors votre récompense d'avoir tout lu jusqu'ici, et moi d'avoir écrit ce billet :-)

Stay tuned !

[EDIT: voir cet autre billet plus récent sur les class helpers]

C# fait la pluie et le beau temps !

La pluie et le beau temps, je parle de la météo et de ma station Lacrosse, vous l'aurez compris.
Et quand un geek possède un tel engin, par force il sort son compilateur pour bricoler. Inévitable.

Il tripote, il essaye et par force il y a des ratés, donc besoin de réparer les fichiers historiques issus de la machine.

Comme je sais n'être pas le seul à posséder une Lacrosse série 2300-2350 et que réparer le fichier "history.dat" est certainement un besoin courant et non comblé par les logiciels fournis avec la station, voici deux utilitaires :

  • History Inspector
    pour inspecter, modifier les données, consolider les données du pluiviomètre, ...
  • DatMerger
    pour fusionner deux historiques avec consolidation des données.

Il s'agit de logiciels fournis à titre expérimental, sans aucune garantie ni aucun support même si je reste à l'écoute de vos retours.

Le code source est fourni, deux projets VS 2005 (tout le monde n'a peut-être pas encore VS 2008) en C#.
Les séquences de lecture/écriture ont été reprises du logiciel eWs2300 d'Antoine Guilmard avec quelques modifications (donc ne mélanger les sources!) que je tiens à remercier pour la publication de son code.

C'est gratuit, avec le source. Les exécutables sont fournis aussi pour ceux qui n'ont pas VS2005/2008 sous la main. Mais il faudra alors installer le runtime de .NET 2.0 au minimum (téléchargeable gratuitement sur les sites de Microsoft).

Le tout est à télécharger à cette adresse : [consultez la page "Ressources" du site e-naxos en cliquant sur le logo "e-naxos" en haut à droite !]