Dot.Blog

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

Mettre des données en forme en une requête LINQ

Losqu'on traite de LINQ, la majorité des exemples utilisent des sources de données "bien formées" : flux RSS (mon dernier billet), collections d'objets, etc. Mais dans la "vraie vie" les sources de données à traiter sont parfois moins "lisses", moins bien formées. LINQ peut aussi apporter son aide dans de tels cas pour transformer des données brutes en collection d'objets ayant un sens. C'est ce que nous allons voir dans ce billet.

Prenons un exemple simple : imaginons que nous récupérons une liste de rendez-vous depuis une autre application, cette liste est constituée de champs qui se suivent, les initiales de la personne, son nom et l'heure du rendez-vous. Tous les rendez-vous se suivent en une longue liste de ces trois champs mais sans aucune notion de groupe ou d'objet.

Pour simplifier l'exemple, fixons une telle liste de rendez-vous dans un tableau de chaînes :

string[] data = new[] { "OD", "Dahan", "18:00", "MF", "Furuta", "12:00", "BG", "Gates", "10:00" };

La question est alors comment extraire de cette liste "linéaire" les trois objets représentant les rendez-vous ?

La réponse passe par trois astuces :

  • L'utilisation de la méthode Select de LINQ qui sait retourner l'index de l'entité traitée
  • La syntaxe très souple de LINQ permettant d'utiliser des expressions LINQ dans les valeurs retournées
  • Et bien entendu la possibilité de créer des types anonymes

Ce qui donne la requête suivante :

var personnes = data.Select ( (s, i) => new
                                          {
                                             Value = s,
                                             Bloc = i / 3
                                          }
                                        ).GroupBy(x => x.Bloc)
                                        .Select ( g => new 
                                                    {
                                                      Initiales = g.First().Value,
                                                      Nom = g.Skip(1).First().Value,
                                                      RendezVous = g.Skip(2).First().Value
                                                     } );

L'énumération suivante : 

foreach (var p in personnes.OrderBy(p=>p.Nom)) Console.WriteLine(p);

donnera alors cette sortie console de toutes les personnes ayant un rendez-vous, classées par ordre alphabétique de leur nom :

{ Initiales = OD, Nom = Dahan, RendezVous = 18:00 }
{ Initiales = MF, Nom = Furuta, RendezVous = 12:00 }
{ Initiales = BG, Nom = Gates, RendezVous = 10:00 }

Voici des objets bien formés (on pourrait ajouter un DateTime.Parse à la création du champ RendezVous pour récupérer une heure plutôt qu'une chaîne) qui pourront être utilisés pour des traitements, des affichages, une exportation, etc...

LINQ to Object ajoute une telle puissance à C# que savoir s'en servir au quotidien pour résoudre tous les petits problèmes de développement qui se posent permet réellement d'augmenter la productivité, ce que tous les langages et IDE promettent falacieusement (aucune mesure de ce supposé gain n'existe). Ici, essayez d'écrire le même code sans LINQ, vous verrez tout suite que le gain de productivité et de fiabilité est bien réel, et que la maintenance aura forcément un coup moindre.

Pour d'autres infos, Stay Tuned !

Le projet VS 2008 : LinqChunk.zip (5,35 kb)

Traiter un flux RSS en 2 lignes ou "les trésors cachés de .NET 3.5"

.NET 3.5 apporte beaucoup de classes nouvelles et d'améliorations à l'existant. Certains ajouts sont plus médiatisés que d'autres. Mais il serait injuste de limiter cette mouture à LINQ ou aux arbres d'expressions, aussi géniales et puissantes soient ces avancées.

.NET 3.5 apporte réellement une foule de nouveautés parmi lesquelles il faut noter :

  • L'apport de WCF et de LINQ au compact framework
  • De nouvelles facilités pour contrôler le Garbarge Collector comme le LatencyMode de la classe GCSettings
  • L'ajout de l'assemblage System.NetPeerToPeer.Collaboration qui permet d'utiliser les infrastructures de peer-to-peer
  • Des améliorations importantes de WCF, l'intégration WCF-WF
  • etc...

Pour une liste complète des nouveautés il est intéressant de jeter un oeil à cette page MSDN.

Un exemple qui illustre les avancées plus ou moins méconnues de .NET 3.5, l'espace de noms System.ServiceModel.Syndication dans la dll System.ServiceModel.Web apporte de nouvelles facilités pour gérer des flux RSS. Et pour s'en convaincre quelques lignes de codes :

SyndicationFeed feed;
using (var r = XmlReader.Create(https://www.e-naxos.com/Blog/syndication.axd))
{ feed = SyndicationFeed.Load(r); }

C'est tout ! Dans la variable "feed" on a tout ce qu'il faut pour travailler sur le flux RSS.

Vous pensez que je triche et que "travailler sur le flux RSS" c'est certainement pas si simple que ça ?. Bon, allez, c'est parce que c'est vous : compliquons même la chose et, à partir de ce flux, affichons le titre de tous les billets qui parlent de LINQ dans leur corps. Voici le code complet près à compiler :

using System;
using System.Linq;
using System.ServiceModel.Syndication;
using System.Xml;

namespace NET35RSS
{ class Program
  {
     static void Main()
     {
        SyndicationFeed feed;
        using (var r = XmlReader.Create(
https://www.e-naxos.com/Blog/syndication.axd))
        { feed = SyndicationFeed.Load(r); }
           if (feed==null) { Console.WriteLine("Flux vide."); return; }
           Console.WriteLine(feed.Title.Text);
           Console.WriteLine(feed.Description.Text+"\n");
           var q = from item in feed.Items
           where item.Summary.Text.ToUpper().Contains("LINQ") select item;
           foreach (var item in q) Console.WriteLine(item.Title.Text);
       }
    }
}

Ajoutez une petite saisie pour le mot à chercher au lieu d'un codage en dur ("LINQ" dans cet exemple) et un petit fichier paramètre pour y stocker la liste des blogs que vous connaissez, et en deux minutes vous aurez un puissant outil de recherche capable de vous lister toutes les billets de vos blogs préférés qui parlent de tel ou tel sujet...

C'est pas génial ça ?

Si, ça l'est, bien sûr ! Alors Stay Tuned !

pour les paresseux du clavier, le projet VS 2008 : NET35RSS.zip (5,36 kb)

Le retour des sous-procédures avec les expressions Lambda...

La syntaxe de C# et son orientation "tout objet" ont définitivement tourné la page de la programmation procédurale. Ce n'est certes pas un mal, bien au contraire, mais au passage nous avons perdu une petite facilité de langages tel que Pascal qui autorisaient la déclaration de procédures à l'intérieur de procédures. Le manque n'est pas cruel mais tout de même... Il semble souvent bien lourd et assez artificiel d'être obligé de créer une méthode private ou internal juste pour rendre un service à une seule méthode. De plus le morceau de code ainsi transformé en méthode, même private ou internal, ne sera encapsulé qu'au niveau de la classe et non de la méthode intéressée, d'où le risque de l'utiliser ailleurs (ce qui change sa statégie d"écriture). Le code sera aussi plus lourd en raison de la nécessité de passer en paramètre tout ce qui sera nécessaire à l'exécution de cette "sous méthode" alors qu'une procédure imbriquée peut référencer les variables de la procédure qui l'abrite.

Bref, l'affaire ne mérite certainement pas de grandes théories, mais il faut avouer que de temps en temps on aimerait bien pouvoir déclarer une petite méthode à l'intérieur d'une autre. Il s'agit là d'appliquer la logique des classes elle-mêmes : il est possible de déclarer une classe dans une autre lorsqu'elle ne sert exclusivement qu'à la première. Pourquoi ne pas retrouver cette possibilité au niveau des méthodes d'une classe ?

Les procédures imbriquées n'existent pas en C#. Cela vous semble une certitude. Avec C# 3.0 ce n'est plus aussi certain...

Les expressions Lambda utilisées comme des procédures imbriquées

Je n'entrerai pas ici dans le détail de la syntaxe des expressions Lambda que j'ai déjà présenté dans un long article sur les nouveautés de C# 3.0, article auquel je renvoie le lecteur s'il en ressent le besoin (Les nouveautés syntaxiques de C# 3.0 et Présentation des différentes facettes de LINQ)

Les procédures imbriquées ne sont rien d'autres que des procédures "normales" mais déclarées à l'intérieur d'autres procédures. En Pascal cela ne peut se faire qu'entre l'entête de la méthode principal et le corps de celle-ci :

Procedure blabla
 Procedure imbrique begin ... end;
begin // blabla
...
end; // blabla

Avec les expressions Lambda de C# 3.0 on retrouve une possibilité sensiblement identique avec plus de souplesse encore puisque la "sous procédure" peut être déclarée n'importe où dans le corps de la "procédure principale".

Exemple

static void Main(string[] args)
{
    // une "fonction" imbriquée (teste si un nombre est impair)
   Func<int, bool> isOdd = i => (i & 1) == 1;
   
// une "procédure" imbriquée (formate et écrit un int à la console)
   Action<int> format = i => Console.WriteLine(i.ToString("000")); 
   
   Console.WriteLine(isOdd(25));
   Console.WriteLine(isOdd(24));

   format(25);
   format(258);
   format(5);
}

La sortie sera :

True
False
025
258
005

Conclusion

La possibilité de déclarer des "sous procédures" est bien pratique, cela permet en général d'éviter les répétitions dans le coprs d'une méthode, donc de diminuer le risque de bug et d'améliorer sensiblement la lecture du code. C# ne supportait pas cette possibilité syntaxique, mais en utilisant les expressions Lambda nous retrouvons la même fonctionnalité...

Pour d'autres astuces, Stay Tuned !

Et pour les paresseux le projet VS 2008 : SubRoutines.zip (4,73 kb)

Un éradicateur de fichiers dupliqués gratuit !

Smarter Duplicate File Finder est un outil gratuit très malin : il permet de localiser les fichiers dupliqués dans une liste de répertoires pouvant se situer éventuellement sur des disques différents.

L'intelligence de SDFF se situe à plusieurs niveaux dont la recherche elle-même. Pour localiser les fichiers dupliqués l'utilisateur dispose de plusieurs méthodes : par le nom (casse indifférente), par le type et la taille, par un code de proximité phonétique sur le nom (4 algorithmes : soundex, metaphone, double metaphone et Daitch-Mokotoff, longueur de clé paramétrable de 2 à 50) ou bien par comparaison des contenus par le biais d'une clé de hash MD5 calculée à la volée.

On peut envoyer la liste des fichiers dans le presse-papiers, les déplacer vers tout répertoire ou bien les envoyer à la corbeille de Windows.

La sélection des fichiers montre aussi de l'intelligence : lorsque SDFF créé la liste des doublons il affecte à chaque groupe un code "groupe de proximité" pour pouvoir les traiter séparément. Il devient ainsi possible en un seul clic de sélectionner, pour le conserver, le plus gros, le plus petit, le plus récent ou le plus vieux fichier de chaque groupe. Il est bien entendu possible de sélectionner les fichiers à garder à la main (chaque fichier dans la liste est précédée d'une case à cocher).

Bref, un outil bien pratique. Il est tellement bien que je l'ai adopté tout de suite pour supprimer les doublons de ma collection de mp3 ou bien supprimer les fichiers dupliqués de dossiers clients copiés sur plusieurs disques. Bon, c'est vrai, j'aurais eu du mal à ne pas l'aimer cet outil puisque c'est moi qui l'ai fait :-)

Développé sous VS 2008 en C# 3.0 et framework 3.5 (que le setup installe si vous ne l'avez pas) et faisant appel à beaucoup de LINQ to Object, SDFF est auto-localisé en français et en anglais (mais on peut le forcer dans une langue par la ligne de commande). Le code source n'est pas distribué pour le moment, je travaille encore dessus.

Pour télécharger l'installeur msi c'est ici : https://www.e-naxos.com/download/SDFF1SetupFR.msi

C'est pas un joli cadeau ça ?

Alors Stay Tuned !

(et n'hésitez pas à me laisser un mot si vous avez des idées d'amélioration)

Une petite capture d'écran pour se faire une idée :

Nota: les mp3 de l'exemple sont tous enregistrés via Screamer dont je vous ai parlé dans mon billet coup de gueule sur le piratage. C'est donc du légal, enregistré à la radio et pas du donwload de la mule. Le résultat est rigoureusement le même, sauf que dans un cas on est un bon gars et dans le second on est passible de poursuites, de prison, d'amende et peut être même d'être fouetté en place publique par le patron d'une major. ça démontre la bêtise de la répression et des lois qui se contredisent. ne piratez pas, enregistrez des radios Internet achetez chez des labels indépendants et n'achetez plus rien aux majors ! 

Un excellent livre sur LINQ : "LINQ in Action"

LINQ (encore ! dirons certains, mais c'est justifié je vous l'assure) est une technologie qui décuple la puissance de C#, encore faut-il bien maîtriser toutes les nouveautés syntaxiques de C# 3.0 (voir mes articles sur ce sujet*) et bien comprendre comment tirer partie du meilleur de LINQ lui-même.

Pour cela rien ne vaut un bon livre. Sur le sujet ils ne sont pas très nombreux et quand on fait un peu le tri, il en reste fort peu qui valent l'achat. Ce n'est pas le cas de "LINQ in action" un excellent bouquin qui utilise une approche très pédagogique pour "monter en puissance" au fur et à mesure de la progression des chapitres. C'est d'ailleurs peut-être aussi parce que les auteurs reprennent justement cette logique que j'applique dans mes propres cours sur LINQ que j'apprécie ce livre.

Il s'agit bien entendu d'un ouvrage en anglais, si vous ne maîtriser pas la langue il faudra hélas passer votre chemin. Les concepts avancés dans le livre peuvent s'avérer suffisament sophistiqués en eux-mêmes sans ajouter la difficulté de décryptage si on n'est pas totalement à l'aise avec la prose d'outre-Atlantique.

Pour acheter le livre au moins cher, je vous conseille l'excellent librairie anglaise The Book Repository qui livre en France et qui applique des tarifs généralement hors concurrence. La page du livre est ici "Linq in Action". L'ouvrage possède aussi son propre site avec le téléchargement des exemples ainsi qu'un forum.

(* Les nouveautés syntaxiques de C# 3.0 et Présentation des différentes facettes de LINQ )

LINQ et Réflexion pour obtenir les valeurs de System.Drawing.Color

LINQ... l'un de mes sujets préférés... Plus je l'utilise, à toutes les sauces, et plus je trouve cette technologie indispensable. Mais foin de propagande, voici encore un exemple qui prouve à quel point LINQ peut servir à tout, facilement.

Le but du jeu : récupérer la définition des couleurs de System.Drawing.Color, en faire un tableau trié par ordre alphabétique.

A la "main" je vous souhaite bien du plaisir... Avec LINQ cela donne la chose suivante :

namespace LinqColor
{
  class Program
  {
     static void Main(string[] args)
    {
       var color_type = typeof(System.Drawing.Color);
       var color_properties = from prop in color_type.GetProperties()
                                     where prop.PropertyType == color_type
                                     orderby prop.Name
                                     select prop;
       
       foreach (var color_property in color_properties)
       {
           var cur_color = (System.Drawing.Color) color_property.GetValue(null, null);
           uint rgb_int = (uint) cur_color.ToArgb() & (0x00ffffff);
           Console.WriteLine(" {0,-20} = ({1,-3},{2,-3},{3,-3}) = 0x{4:X00000000}",
           color_property.Name,
           cur_color.R, cur_color.G, cur_color.B,rgb_int);
       }
    }
  }
}

La sortie écran (console) donne la chose suivante :

Le projet VS 2008 pour les fénéants du clavier : LinqColor.zip (5,43 kb)

C'est pas merveilleux LINQ ?

Bien sûr que si... Alors Stay Tuned !

Internationalisez vos blogs et pages Web en 1 seul click avec Windows Live Translator

On aimerait parfois offrir certaines informations dans une langue étrangère mais traduire toutes les pages d'un site représente un coût non négligeable que seules de très grosses entreprises aux moyens financiers à la mesure d'une telle opération de communication peuvent se permettre.

Il existe toutefois une parade, certes pas absolue mais très pratique, elle s'appelle Windows Live Translator. En ajoutant un simple bout de script à vos pages les visiteurs pourront les consulter dans leur langue. J'ai ajouté cette fonction au présent blog (tout en haut à gauche), vous pouvez donc "jouer" avec pour vous rendre compte par vous mêmes de la qualité (assez bonne mais très variable malgré tout!) des traductions proposées.

Un Web totalement international, libre et sans frontières, c'est un rêve non ? (sauf si on est au gouvernement chinois bien entendu).

Stay Tuned !