Skip to content

Transformeur (aka Map ou Select) ​

Alt text

Après Filter incarné par le Where de Linq qui permet de filtrer une collection, il est temps d'observer de près d'autres fonctions d'ordre supérieur telles que Select (aka map, transformeur ou projection) .

Transformeur | Map | Select | Projection ​

Inspiré du mots clé SQL de la projection ensembliste SELECT, LINQ propose l'outil Select pour faire des opérations de mapping dans une collection...

Avec notre filtre, nous avons retenu des éléments (objets) qui ont le contenu que l'on veut, mais leur forme n'est pas forcément adaptée à l'usage que l'on veut en faire.

Je ne veux pas transformer, je veux juste sélectionner ​

Le terme transformer peut sembler inadapté comparé à celui de projection, pourtant en sélectionnant une sous-partie d’un ensemble de départ, il y a une transformation...

Selon les langages et librairies, il existe une nuance importante entre transformation et projection dans le sens où la transformation s’applique sur la source directement plutôt que de renvoyer une nouvelle structure de données.

Cet aspect touche à l'immutabilité qui sera présentée ultérieurement....

Select: Choix d'un attribut dans une classe ​

csharp
class Person{
    public string Name{get;set;} 
    public int Age{get;set;}
    public int Sisters{get;set;}
    public int Brothers{get;set;}
  }

List<Person> cid5d = new List<Person>(){
    new Person(){Name="Paul",Age=15,Sisters=2,Brothers=1},
    new Person(){Name="Lucie",Age=18,Sisters=1,Brothers=3},
    new Person(){Name="Claude",Age=16,Sisters=0,Brothers=0}
};

IEnumerable<string> names = cid5d.Select(person => person.Name);//{"Paul","Lucie","Claude"}
List<string> stringNames = names.ToList();

Comme en SQL, on sélectionne une partie des données (ici juste le nom) pour générer une nouvelle liste contenant ceux-ci.

Select: Modification d'une valeur ​

Au moment de produire le résultat, on veut parfois le transformer plutôt que de conserver la valeur brute:

csharp
IEnumerable<int> numberOfSiblings = cid5d.Select(person => person.Sisters+person.Brothers);//{3,4,0}

Cette foi-ci, on génère un nouvel ensemble de valeurs modifiées selon l'ensemble de base...

Classe, Tuple et classe anonyme ​

En sélectionnant des attributs, on crée une nouvelle une nouvelle structure de donnée ... ou pas!

On peut en effet s'appuyer sur une classe déjà existante:

csharp
List<Member> members = cid5d.Select(person => new Member(person.Name,person.Age,person.Sisters,person.Brothers)).ToList();

Tuple ​

Pour récupérer un sous-ensemble d'attributs d'une classe, le tuple peut s'avérer utile:

csharp
var adults = 
    cid5d.Select(person => (person.Name/*Devient Item1*/,person.Age/*Devient Item2*/))
        .Where(tuple=>tuple.Item2>=18); //Item2 correspond à l'age
    
    Console.WriteLine(adults.First().Item1); //Lucie

Le Tuple ressemble à une classe anonyme pouvant supporter n attributs readonly qu'on accède avec les propriétés Item1, Item2, Item3, ItemN un peu comme avec un tableau...

csharp
var tuple = (1,2,3);
Console.WriteLine(tuple.Item1);//1
Console.WriteLine(tuple.Item2);//2
Console.WriteLine(tuple.Item3);//3

Il est possible de nommer les attributs d'un tuple

csharp
var tuple = (first: 1, second: 2,third: 3);
Console.WriteLine(tuple.first);//1
Console.WriteLine(tuple.second);//2
Console.WriteLine(tuple.third);//3

ATTENTION toutefois car un ToList() fera disparaître cette information...

Classe anonyme ​

À défaut du tuple, il est aussi possible de générer une classe anonyme qui gardera ses informations sur toute la chaîne d’appel LINQ:

csharp
var anon = new {first= 1, second= 2,third= 3};
Console.WriteLine(anon.first);//1
Console.WriteLine(anon.second);//2
Console.WriteLine(anon.third);//3

En détail ​

Pour plus de détail, consulter la documentation officielle

Alt text

Transformeurs communs ​

LINQ propose quelques transformeurs utiles:

  • GroupBy
  • ToList
  • ToArray
  • ToDictionary

GroupBy ​

Comme son nom l'indique, il groupe selon un critère et renvoie un dictionnaire dont chaque entrée a:

  • Une clé
  • La collection des élément ses éléments qui satisfont le critère (sur laquelle on va pouvoir appliquer des aggrégateurs tels que Count, Sum, Max, Min, ....)

Voir l'exemple dans la documentation .NET

ToList ​

Convertit l'entrée (de type IEnumerable) en liste (de type List<T>).

ToDictionary ​

Crée un dictionnaire selon la fonction d'affectation pour la clé et la valeur.

Cela peut s'avérer utile dans certaines situations, par exemple pour retrouver plus rapidement une information. En effet, retrouver une personne par index dans un dictionnaire comme ceci:

csharp
Dictionary<int, Person> dict;
Person person = dict[712]

est beaucoup plus rapide qu'une recherche LinQ comme cela

csharp
List<Person> people;
Person person = people.Where(p => p.Id == 712).First();

Si vous en doutez, essayez ceci...

SelectMany — le flatMap ​

Select transforme chaque élément en un résultat : une collection de N éléments donne toujours N résultats. Mais que faire si chaque élément produit plusieurs résultats — une collection de collections ?

csharp
// Chaque joueur a une liste de matchs
var teams = new[]
{
    new { Player = "Léa",     Matches = new[] { "m1", "m2", "m3" } },
    new { Player = "Raphaël", Matches = new[] { "m4", "m5" } },
};

// Select → une séquence DE séquences (imbriquée)
IEnumerable<string[]> nested = teams.Select(t => t.Matches);
// { ["m1","m2","m3"], ["m4","m5"] }

// SelectMany → une seule séquence aplatie (d'où le nom flatMap dans d'autres langages)
IEnumerable<string> flat = teams.SelectMany(t => t.Matches);
// { "m1", "m2", "m3", "m4", "m5" }

Contraste :

Signature du sélecteurRésultat
SelectT → TResultN éléments → N résultats
SelectManyT → IEnumerable<TResult>N éléments → tous les résultats concaténés

SelectMany est un map qui aplatit : il applique le sélecteur puis fusionne les séquences produites en une seule — parfait pour agréger les matchs de toute une équipe en un seul pipeline.

Composition de pipelines ​

Enchaîner Where et Select revient à composer des fonctions : f(g(x)) en maths devient x.g().f() en LINQ.

csharp
// Pipeline composé : chaque étape retourne le même type
temperatures
    .Where(t => t > -50)       // Filter — retourne IEnumerable<double>
    .Select(t => t + 273.15)   // Map (Celsius → Kelvin) — retourne IEnumerable<double>
    .Where(k => k < 400)       // Filter encore

La composition est possible uniquement parce que chaque méthode retourne une nouvelle valeur au lieu de modifier la source. L'immutabilité rend la composition possible.

Closures dans les transformations ​

Dans .Select(t => t + offset), si offset est une variable du scope parent, c'est une closure. La même règle que pour Where s'applique : la valeur capturée est lue au moment de l'exécution du pipeline, pas au moment de sa construction.

csharp
double factor = 2.0;
var doubled = numbers.Select(n => n * factor); // closure sur factor

factor = 3.0;
var result = doubled.ToList(); // exécution ici — utilise factor=3.0, pas 2.0 !