LINQ
Fonctionnement de base
Arrivées avec la version 3 du langage C#, les lambdas ont bouleversé le quotidien des développeurs. Une lambda est une façon d’écrire une fonction directement au sein d’une ligne sans avoir à la définir explicitement au sein de la classe. L’avantage de cette approche est que cette fonction peut dès lors être utilisée comme variable et être exécutée par une autre fonction, voire même passée directement à une fonction. Cette nouvelle façon d’écrire une fonction (aussi appelée parfois fonction fléchée) est un raccourci fort apprécié et largement adopté. De plus, lors de la version 3 du framework .NET, Microsoft a inclus un nouveau système de gestion des collections : LINQ (pour Language INtegrated Query).
Les développeurs sont souvent confrontés à l’usage d’une base de données lors de la création d’un projet. Beaucoup connaissent le langage SQL, norme répandue pour les systèmes de gestion de bases de données relationnelles.
Dans une optique de mutualisation des compétences, Microsoft a introduit LINQ dans le framework .NET pour permettre aux développeurs .NET de considérer toute collection comme étant une base de données et d’avoir...
Variables anonymes
C# est un langage fortement typé, ce qui fait qu’un objet d’un type donné ne peut pas changer de type au cours de son existence. Néanmoins, avec l’arrivée de LINQ, il arrive que le développeur souhaite récupérer un ensemble de valeurs ayant un regroupement logique sans pour autant en faire une classe ou une structure définie.
Avec la version 3 du C#, le concept de variable anonyme a été introduit. Il s’agit de créer un objet, fortement typé, mais qui n’appartient pas un type connu ni déclaré. Cette flexibilité permet de créer un objet sur mesure, qui respecte toujours toutes les contraintes du typage fort du langage. Par exemple, si nous souhaitons créer une voiture sans pour autant avoir à définir une classe pour héberger les données relatives, nous pouvons écrire le code suivant :
var voiture = new { Marque = "Peugeot", Chevaux = 120, Fonctionne = true };
Comme nous le constatons, il n’y a pas de type après l’instruction new. L’ouverture des accolades permet de définir directement la liste des propriétés que va contenir cet objet. Cette flexibilité est très utile pour générer à la volée un objet en retour de requête LINQ, afin de produire...
Principes des opérateurs LINQ
LINQ met nativement à disposition un grand nombre d’opérateurs. Si la liste proposée ne suffit pas à votre usage, un projet communautaire en ajoute beaucoup d’autres : https://github.com/morelinq/MoreLINQ. Néanmoins, nous nous contenterons d’étudier dans ce chapitre ceux qui sont fournis nativement dans le framework.
Tout d’abord, il faut comprendre que LINQ fonctionne dans un ordre inverse de celui de SQL. En effet, une requête SQL simple est souvent formulée de la sorte :
SELECT ... FROM Table WHERE ... ORDER BY ...
En traduisant cette requête en français, nous obtenons la logique suivante : dans la table Table (FROM), où j’applique telle condition (WHERE), en ordonnant les lignes selon tel ordre (ORDER BY), je souhaite récupérer les colonnes suivantes (SELECT).
Les opérateurs LINQ prennent en paramètre une lambda définissant ce qui est attendu par la méthode. La majorité des lambdas pour LINQ sont formées de la façon suivante : la lambda prend en paramètre l’instance actuelle qui correspond à l’élément courant de la collection et l’utilise dans le corps de la fonction. Il est possible de passer par une fonction classique pour obtenir le même résultat. Ainsi, les deux façons d’écrire suivantes sont similaires :
public void TestLinq()
{
List<int> l = new()
{
1, 2, 3, 4, 5, 6, 7, 8, 9
};
var entiers = l.Where(i => i < 3);
var entiers2 = l.Where(Filtre);
}
public bool Filtre(int i)
{
return i < 3;
}
Le deuxième cas est moins courant car nous passons en paramètre à l’opérateur LINQ le nom de la fonction qui doit être appelée pour le filtre. Cela ne fonctionne que si la fonction a les bons nombre et types de paramètres et type de retour, c’est pourquoi il est recommandé...
Expression de requête LINQ
Jusqu’à présent, nous avons couvert LINQ par le prisme des méthodes d’extension sur l’interface IEnumerable<T>. Cependant, LINQ propose une autre syntaxe, appelée "expression de requête" (ou query expression en anglais). Cette dernière se rapproche d’un formalisme ressemblant davantage à ce qui existe en SQL, même si l’ordre logique est inversé.
En effet, lorsque nous utilisons cette approche, il faut commencer par décrire le nom de la variable avec laquelle nous souhaitons travailler dans une collection donnée, en utilisant le formalisme from ... in ....
À la suite de cette extraction, il est possible d’ajouter un ou plusieurs opérateurs :
-
where.
-
orderby/orderby ... descending. Par défaut, l’opérateur order by fonctionne de façon ascendante sans qu’il soit nécessaire de le préciser. Néanmoins, il est possible d’ajouter le mot-clé ascending pour être explicite.
-
thenby/thenby ... descending. Par défaut, then by fonctionne de façon ascendante sans qu’il soit nécessaire de le préciser. Néanmoins, il est possible d’ajouter le mot-clé ascending pour être explicite.
-
group ... by ....
-
join.
La requête se termine par un select.
Par exemple :
var entiers = new List<int> { 1, 2, 3, 4, 5 };
var tri = from i in entiers where i > 2 orderby i descending select
i.ToString(); // on aura une collection de chaînes, qui contient
les entiers supérieurs à 2, triés en ordre décroissant
Comme nous pouvons le constater, cette façon d’écrire une requête LINQ se rapproche du SQL, exception faite de l’inversion de la logique de sélection (l’opérateur select est en dernier en LINQ alors qu’il arrive en premier en SQL).
Pour les requêtes LINQ simples, il n’y...
Exercice
Il est temps de mettre en pratique ce qui a été vu dans ce chapitre sur LINQ.
1. Énoncé
Cet exercice consiste à générer (à l’aide des opérateurs LINQ) une liste d’entiers, de 1 à 100, et de ne retourner que la liste des nombres qui sont divisibles par 7. Afin que l’exercice ne soit pas trop simple, il est demandé d’écrire les requêtes LINQ sous les deux formes qui ont été étudiées : par le biais des méthodes d’extension et avec l’expression de requête.
Les nombres attendus en résultat sont 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91 et 98.
Une fois ce résultat obtenu, il faudra le scinder en deux groupes : les nombres pairs et les nombres impairs. Chacun des groupes obtenus est identifié par son nom (pair ou impair) et contient les nombres concernés.
Voici un exemple d’affichage de ce qui est attendu :
![images/05RI03N.png](https://www.eni-training.com/download/db6f6212-eb4d-4d34-b627-6839c3ea70c3/images/05RI03N.png?id=AAEAAAD%2f%2f%2f%2f%2fAQAAAAAAAAAMAgAAAE1FbmkuRWRpdGlvbnMuTWVkaWFwbHVzLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAJ0VuaS5FZGl0aW9ucy5NZWRpYXBsdXMuQ29tbW9uLldhdGVybWFyawIAAAAHcGlzVGV4dAlwaWR0ZURhdGUBAA0CAAAABgMAAAAwdGVzdCB2Ml84IC0gZmZhNjQzOWQtZDZjYi00NzYxLWJhYTMtZWZkZDNiZDcwMWM25rSq0F7p2IgL)
Résultats d’exécution attendus pour l’exercice
La double présence des chiffres en haut s’explique par le fait que les deux requêtes LINQ ont été itérées (celle avec les méthodes d’extension et celle avec l’expression de requête LINQ). Pour obtenir un décalage régulier entre les chiffres, on peut utiliser la tabulation, en insérant...