LINQ (Language Integrated Query) est un composant intégré au .NET Framework depuis la version 3.5. Il ajoute aux langages utilisant le .NET Framework tels que le C#, le VB.NET et bien d’autres des méthodes permettant de manipuler les classes énumérables (les tableaux, les listes, les piles, les files, …) beaucoup plus facilement. Voici un exemple d’utilisation :
string[] names = new [] { "Amélie", "Bernard", "Benoit", "Pascal", "Xavier" };
foreach (string name in names.Where(n => n.StartsWith("B")))
Console.WriteLine(n); // Affiche "Bernard" et "Benoit"
Comme vous pouvez le voir, avec une syntaxe particulièrement succinte et facilement compréhensible, il est possible d’effectuer des opérations d’habitude fastidieuses.
Tout d’abord, pour comprendre correctement la syntaxe LINQ, il est nécessaire de savoir écrire des expressions lambda. Vous avez sûrement du remarquer l’expressionn => n.StartsWith("B")
 qui n’est pas très familière à première vue. Nous allons expliquer ces expressions dans un premier temps.
En C# et dans les autres langages .NET, les fonctions sont des objets, c’est-à -dire qu’il est tout à fait possible de passer des fonctions en paramètre, ou de les stocker dans une variable, comme ceci :
public int Square(int x)
{
return x * x;
}
public void Main(params string[] args)
{
var a = Square;
Console.WriteLine(Square(10));
Console.WriteLine(a(10));
}
Vous allez me dire "mais si les fonctions sont des variables, ça veut dire qu’elles ont aussi un type non ?". Exactement, il existe en fait deux types pour représenter les fonctions : Action et Func.
Action et Func sont des types génériques afin de pouvoir spécifier le type de la valeur de retour et des paramètres, par exemple :
public int Square(int x)
{
return x * x;
}
public void Main(params string[] args)
{
Func a = Square;
Action b = Main;
}
Il existe une autre notation pour écrire des fonctions, la notation lambda qui permet d’écrire une fonction dans votre code, tout comme un string ou encore un int. Cette notation est composée de deux parties distinctes séparées par le signe =>. La partie gauche correspond aux paramètres de la fonction et la partie droite à la valeur de retour de la fonction. Si la fonction ne possède aucun paramètre, on utilisera () pour la partie gauche. Si elle en comprend plusieurs, on utilisera cette notation : (a, b, c). Il est d’usage de n’utiliser uniquement des lettres afin de conserver la compacité du code et de facilité la compréhension.
Tout comme dans une fonction classique, on peut appeler les paramètres avec n’importe quel nom, mais avec une nouvelle règle, ce nom ne doit pas rentrer en conflit avec d’autres variables du bloc courant. Voici notre chère fonction Square écrite en notation lambda :
public void Main(params string[] args)
{
Func Square = x => x * x;
Console.WriteLine(Square(10));
}
Voici quelques autres exemples pour vous familiariser avec cette notation :
Func Rand = () => new Random().Next();
Action Write = s => Console.WriteLine(s);
Func Odd = n => n % 2 == 1;
Maintenant que nous comprenons et que nous pouvons écrire des expressions lambda, revenons à LINQ. Vous pouvez certainement déjà comprendre la signification du premier exemple que je vous ai montré. En effet, la fonction Where prend en paramètre une autre fonction. J’aurais pu écrire l’exemple sous cette forme équivalente, bien que beaucoup plus longue :
public bool StartWithB(string name)
{
return name.StartsWith("B");
}
public void Main(params string[] args)
{
string[] names = new [] { "Amélie", "Bernard", "Benoit", "Pascal", "Xavier" };
foreach (string name in names.Where(StartWithB))
Console.WriteLine(n);
}
Le fonctionnement de LINQ est simple, il ajoute donc des fonctions aux classes énumérables qui parcourent leurs éléments et applique une fonction sur chacun d’eux. Elle agit en fonction du résultat de la fonction donnée en paramètre. Voici d’autres exemples de fonctions LINQ et leur description :
Certaines fonction LINQ (notament Where ou OrderBy renvoient eux-même des collections, permettant d’enchainer les appels aux fonction LINQ. Voici quelques examples d’utilisaiton des fonctions citées ci-dessus (en utilisant la collection names) :
// Tri des noms selon la deuxième lettre
names.OrderBy(n => n[1]); // Renverra "Pascal", "Xavier", "Bernard", "Benoit", "Amélie"
// Idem, puis recuperation du premier élément contenant "er"
names.OrderBy(n => n[1]).First(n => n.Contains("er")); // Renverra "Xavier"
// Décompte des noms commencant par la lettre B
names.Count(n => n.StartsWith("B")); // Renverra 2
// Affichage de la liste des premières lettres des noms, sans doublons
names.Select(n => n.SubString(0, 1)).Distinct(); // Renverra A B P X
Il existe une seconde notation pour utiliser les fonctions fournies par LINQ. Vous avez certainement du remarquer qu’une grande partie des fonctions LINQ sont inspirées du langage SQL, qui s’avère très efficace sur la gestion des bases de données (comparables aux collections). Depuis le .NET Framework 3.5, il est possible d’utiliser une syntaxe similaire au SQL dans nos programmes C#, voici un exemple et son équivalent en notation classique :
// Tri inverse des noms selon la troisième lettre et affichage des trois premières lettres
from n in names
where n.StartsWith("B")
orderby n[2] descending
select n.Substring(0, 3); // Renverra "Ber" et "Ben"
// Notation classique
names.Where(n => n.StartsWith("B")).OrderByDescending(n => n[1]).Select(n => n.SubString(0, 3));
Effectivement, on remarque directement la ressemblance avec le langage SQL. À noter que cette notation ne peut être utilisée que pour renvoyer des collections, pour remplacer des fonctions comme Where, OrderBy, Select.
On peut décomposer cette notation en trois blocs :
from n in names
. On spécifie ici la collection sur laquelle on souhaite travailler et le nom de la variable utilisée pour représenter un élément de la collection. La syntaxe est similaire au foreach.
where n.StartsWith("B")
. On peut utiliser plusieurs filtres tels que where, orderby. On utilise la variable définie ci-dessus pour définir les filtres.
select n.Substring(0, 3)
 où l’on spécifie la valeur qui sera renvoyée. C’est l’équivalent de la fonction Select.
Il est bon de noter que certains types sont nativement des collections. Par exemple le type string est une collection de char, il est donc possible d’utiliser les fonctions LINQ sur une chaine de caractère.
Voici une liste des fonctions fournies par LINQ les plus utilisées et des exemple pour comprendre plus aisément leur fonctionnement (les exemples ci-dessous utilisent la collection names définie plus haut) :
// Récupération des trois premières lettres de chaque nom
names.Select(n => n.SubString(0, 3)) // Renverra "Amé", "Ber", "Ben", "Pas", "Xav"
// Récupération de la liste des lettres utilisées dans chaque noms
// Chaque string étant une collection de char, le SelectMany récupère directement la liste des lettres des noms
names.SelectMany(n => n.ToLower()).Distinct().OrderBy(l => l); // Renverra a, b, c, d, e, i, l, m, n, o, p, r, s, t, v, x, Ă©
// Taille totale des noms
names.Sum(n => n.Length) // Renverra 31
// Taille maximale des noms
names.Max(n => n.Length) // Renverra 7
names.Skip(3) // Renverra "Pascal", "Xavier"
names.Take(2) // Renverra "Amélie", "Bernard"
names.First() // Renverra "Amélie"
names.ElementAt(3) // Renverra "Benoit"
names.Reverse() // Renverra "Xavier", "Pascal", "Benoit", "Bernard", "Amélie"
names.Single(n => n.StartsWith("X")) // Renverra "Xavier"
LINQ est un composant fournissant des fonctions supplémentaires pour les collections. Une collection étant un ensemble d’éléments, il est tout à fait possible de construire des collections personnalisées. En suivant ce principe, de nombreuses extensions pour LINQ ont vues le jour.
Par exemple, les bases de données peuvent être considérées comme des collections. Des extensions somme DbLinq ou DotConnect ont été développées. Elles permettent d’interroger directement la base de données avec les fonctions LINQ.
Dans le même esprit, de nombreux services peuvent être considérés comme des collections : pensez à Google, ou encore Twitter. En effet, il existe aussi des connecteurs LINQ pour ces services : Linq2Twitter, GLinq. Voici une liste de quelques services disposant de connecteurs pour vous donner des idées :