Programmation C sharp/Expression de requête
Les versions récentes du langage C sharp permettent d'utiliser une expression de requête sur un ensemble de données itérables en utilisant une syntaxe similaire à celle des requêtes de base de données. Cette intégration de la syntaxe de requête dans le langage s'appelle en anglais Language Integrated Query (LINQ).
Syntaxe
[modifier | modifier le wikicode]Le mot clé from
sert à introduire une expression de requête.
Il est suivi par le nom de la variable qui référencera les valeurs itérées, du mot-clé in
, de l'ensemble de données itéré et d'un nombre d'instructions de requête (sélection, groupement, tri, etc.).
L'ensemble de données doit implémenter l'interface IEnumerable<T>
ou une interface dérivée comme IQueryable<T>
; le type T est alors utilisé par le compilateur pour définir le type générique pour la variable qui référencera les valeurs itérées.
Si l'ensemble de données implémente l'interface IEnumerable
(sans type d'élément spécifié, comme la classe ArrayList
), le type d'élément doit être spécifié.
En clair, cela transforme un ensemble IEnumerable en un autre ensemble IEnumerable itérant des éléments du même type (ou IGrouping<K,T>
pour une clause de groupement), mais filtrant et éventuellement réordonnant les éléments de l'ensemble initial.
Exemple : N'afficher que les entiers pairs d'un tableau
// L'ensemble des données
int[] les_nombres = new int[5] { 12, 11, 24, 28, 17 };
// Requête : nombres_pairs est de type IEnumerable<int>
var nombres_pairs =
from nombre in les_nombres
where (nombre % 2) == 0
select nombre;
// Exécution de la requête : itération sur les résultats
foreach (int nombre_pair in nombres_pairs)
Console.WriteLine("Nombre pair : {0}", nombre_pair);
// N'affiche que les nombres pairs du tableau les_nombres :
// Nombre pair 12
// Nombre pair 24
// Nombre pair 28
La requête n'est pas exécutée immédiatement, mais lors de l'itération avec la boucle foreach
.
Elle est également déclenchée par l'appel à une méthode de l'interface IEnumerable
nécessitant le parcours des éléments :
- Pour obtenir des statistiques sur les résultats de la requête :
Count
,Max
,Average
,First
...
- Pour convertir les résultats en tableau ou collection :
ToArray
,ToList
...
// les_nombres_pairs est de type int[]
var les_nombres_pairs =
(from nombre in les_nombres
where (nombre % 2) == 0
select nombre).ToArray();
La requête semble réaliser l'équivalent d'une boucle combinée à une condition. Cependant elle permet également de trier les résultats, de les regrouper, et il est possible de passer la requête à une fonction ayant besoin d'un ensemble de valeurs itérables. Ces cas d'utilisations seraient beaucoup plus complexes à réaliser sans la syntaxe des expressions de requête.
Quand l'ensemble de données implémente l'interface IEnumerable
sans type d'élément spécifié, comme la classe ArrayList
par exemple, le type d'élément doit être spécifié pour la variable comme dans l'exemple suivant :
ArrayList listeDeNom = new ArrayList();
listeDeNom.add("exemple");
listeDeNom.add("de liste");
listeDeNom.add("contenant");
listeDeNom.add("des valeurs");
var nom_longs =
from string nom in listeDeNom
where nom.length > 8
select nom;
Clauses de requête
[modifier | modifier le wikicode]Différentes clauses suivent la définition de la source de données, traitées dans l'ordre, chacune introduite par un mot-clé :
where
pour filtrer les données avec une condition,join
pour joindre les résultats avec une autre source de données,group
pour grouper les résultats (type T) ayant une valeur commune (la clé K) en objets de typeIGrouping<K,T>
,orderby
pour itérer les résultats dans un ordre particulier,select
pour définir les résultats à itérer (objet, structure, calcul...).
Filtre
[modifier | modifier le wikicode]Le filtrage des éléments est introduit par le mot-clé where
suivi d'une expression booléenne définissant la condition pour que l'élément apparaisse dans l'itération des résultats de la requête.
var petits_nombres_pairs =
from nombre in les_nombres
where ((nombre % 2) == 0) && (nombre < 20)
select nombre;
Plusieurs clauses where
peuvent être utilisées à la suite permettant de valider toutes les conditions, comme l'opérateur et utilisé dans l'exemple précédent.
Exemple équivalent au précédent :
var petits_nombres_pairs =
from nombre in les_nombres
where (nombre % 2) == 0
where nombre < 20
select nombre;
Sélection
[modifier | modifier le wikicode]La clause select
définit la valeur retournée à chaque itération des résultats de la requête, et détermine aussi le type du paramètre générique des résultats itérables.
// Requête : nombres_pairs_romains est de type IEnumerable<string>
var nombre_pairs_romains =
from nombre in les_nombres
where (nombre % 2) == 0
select NombreEnChiffresRomains(nombre);
// Exécution de la requête : itération sur les résultats
foreach (string nombre_pair_romain in nombres_pairs_romains)
Console.WriteLine("Nombre pair : {0}", nombre_pair_romain);
// Si l'hypothétique fonction NombreEnChiffresRomains
// retourne bien la représentation en chiffres romains :
// Nombre pair XII
// Nombre pair XXIV
// Nombre pair XXVIII
Tri
[modifier | modifier le wikicode]Les résultats peuvent être triant en utilisant la clause orderby
.
Ce mot-clé est suivi d'une liste d'expressions définissant chacune la valeur de critère pour définir l'ordre des résultats.
Chacun de ces critères est optionnellement suivi du mot-clé ascending
pour un tri dans l'ordre croissant, ou du mot-clé descending
pour un tri dans l'ordre décroissant.
S'il n'est pas spécifié, le tri est en ordre croissant.
Exemple simple :
// L'ensemble des données
string[] les_mois = new int[12]
{
"janvier", "février", "mars", "avril", "mai", "juin",
"juillet", "août", "septembre", "octobre", "novembre", "décembre"
};
// Requête : les_mois_ordonnes est de type IEnumerable<string>
var les_mois_ordonnes =
from nom_du_mois in les_mois
orderby nom_du_mois ascending
select nom_du_mois;
// Exécution de la requête : itération sur les mois ordonnés
foreach (string mois in les_mois_ordonnes)
Console.WriteLine("- {0}", mois );
// Affiche les mois dans l'ordre alphabétique :
// - août
// - avril
// - décembre
// - février
// - janvier
// - juillet
// - juin
// - mai
// - mars
// - novembre
// - octobre
// - septembre
Exemple avec un tableau d'objets utilisé dans une requête où second critère de tri est spécifié en cas d'égalité pour le premier :
// L'ensemble des données
public class VenteDuMois
{
public string Mois { get; set; }
public List<int> Montants { get; set; }
}
List<VenteDuMois> ventes_par_mois = new List<VenteDuMois>
{
new VenteDuMois { Mois = "janvier", Montants = new List<int> { 220, 158, 301 } },
new VenteDuMois { Mois = "février", Montants = new List<int> { 162, 143, 157 } },
new VenteDuMois { Mois = "mars", Montants = new List<int> { 138, 187, 231 } },
new VenteDuMois { Mois = "avril", Montants = new List<int> { 11, 55, 400 } },
new VenteDuMois { Mois = "mai", Montants = new List<int> { 311, 117, 90, 180 } },
};
// Requête : meilleurs_mois est de type IEnumerable<object>
var meilleurs_mois =
from vente_du_mois in ventes_par_mois
orderby vente_du_mois.Montants.Sum() descending, vente_du_mois.Name ascending
select new { Name=vente_du_mois.Mois, Total=vente_du_mois.Montants.Sum() };
// Exécution de la requête : itération sur les meilleurs mois
foreach (string mois in meilleurs_mois)
Console.WriteLine("- {0} => {1}", mois.Name, mois.Total );
// Affiche les mois dans l'ordre décroissant du total des montants :
// - mai => 698
// - janvier => 679
// - mars => 556
// - avril => 466
// - février => 462
Regroupement
[modifier | modifier le wikicode]Le regroupement est introduit par le mot-clé group
produisant une séquence d'objets de classe implémentant l'interface IGrouping<K,T>
.
Cette interface permet d'accéder à la clé K commune aux éléments membres du groupe par la propriété Key
, et étend l'interface IEnumerable<T>
.
Cela signifie qu'une instance de cette interface permet d'itérer sur les éléments membres du groupe.
Le mot-clé group
est suivi de l'objet groupé (définissant T pour l'interface générique IGrouping<K,T>
), du mot-clé by
suivi de la clé de regroupement (définissant K pour l'interface générique IGrouping<K,T>
).
Exemple : Regrouper les dates d'un planning annuel par mois
// L'ensemble des données
public class DateReunion
{
public string Mois { get; set; }
public int jour { get; set; }
}
List<DateReunion> dates_planning = new List<DateReunion>
{
new DateReunion { Mois = "janvier", jour = 22 },
new DateReunion { Mois = "février", jour = 17 },
new DateReunion { Mois = "mars", jour = 11 },
new DateReunion { Mois = "avril", jour = 15 },
new DateReunion { Mois = "janvier", jour = 12 },
new DateReunion { Mois = "février", jour = 18 },
new DateReunion { Mois = "mars", jour = 8 },
new DateReunion { Mois = "avril", jour = 2 },
new DateReunion { Mois = "janvier", jour = 26 },
new DateReunion { Mois = "février", jour = 16 },
new DateReunion { Mois = "mars", jour = 20 },
new DateReunion { Mois = "avril", jour = 3 }
};
// Requête : planning_mensuel est de type IEnumerable<IGrouping<string,DateReunion>>
var planning_mensuel =
from date_reunion in dates_planning
group date_reunion by date_reunion.Mois; // -> IGrouping<string,DateReunion>
Pour effectuer des opérations supplémentaires sur les groupes, il faut ajouter une clause into
pour définir le nom référençant le groupe, comme dans l'exemple suivant :
// Requête : planning_mensuel est de type IEnumerable<IGrouping<string,DateReunion>>
var planning_mensuel =
from date_reunion in dates_planning
group date_reunion by date_reunion.Mois into g // -> IGrouping<string,DateReunion>
orderby g.Key // par mois dans l'ordre croissant
select g;
Deux boucles imbriquées sont nécessaires : la boucle de premier niveau itère sur les groupes, et la boucle de second niveau itère sur les membres de chaque groupe, comme illustré ci-dessous :
// Exécution de la requête : itération sur les résultats
foreach (IGrouping<string,DateReunion> group in planning_mensuel)
{
Console.WriteLine("Dates en {0} :", group.Key);
foreach (DateReunion date in group)
Console.WriteLine(" le {0} {1}", date.jour, date.Mois);
}
L'exemple affiche les dates groupées par mois :
Dates en avril : le 15 avril le 2 avril le 3 avril Dates en février : le 17 février le 18 février le 16 février Dates en janvier : le 22 janvier le 12 janvier le 26 janvier Dates en mars : le 11 mars le 8 mars le 20 mars
La clé de regroupement est une expression quelconque, et peut créer un nouvel objet pour regrouper plusieurs critères de regroupement.
Combinaison de sources de données
[modifier | modifier le wikicode]Jointure
[modifier | modifier le wikicode]Il est possible de faire une jointure libre en utilisant plusieurs clauses from
sur des sources de données indépendantes, comme dans l'exemple ci-dessous.
string[] projects = { "wikibooks", "wikinews", "wikiversity" };
string[] langs = { "fr", "es", "de" };
var project_domains =
from project in projects
where project != "wikinews" // sauf wikinews
from lang in langs
where project != "wikiversity" || lang != 'de' // sauf (wikiversity, de)
select lang+"."+project+".org";
// Exécution de la requête :
foreach (string domain in project_domains)
Console.WriteLine("- {0}", domain);
// Affiche :
// - fr.wikibooks.org
// - fr.wikiversity.org
// - es.wikibooks.org
// - es.wikiversity.org
// - de.wikibooks.org
Sous-requête
[modifier | modifier le wikicode]Il est possible de créer une sous-requête pour sélectionner des données d'une collection stockée dans un objet.
Pour cela, une clause from
est ajoutée à la première, référençant la variable de celle-ci.
Exemple :
// L'ensemble des données
public class VenteDuMois
{
public string Mois { get; set; }
public List<int> Montants { get; set; }
}
List<VenteDuMois> ventes_par_mois = new List<VenteDuMois>
{
new VenteDuMois { Mois = "janvier", Montants = new List<int> { 220, 158, 301 } },
new VenteDuMois { Mois = "février", Montants = new List<int> { 162, 143, 157 } },
new VenteDuMois { Mois = "mars", Montants = new List<int> { 138, 187, 231 } },
new VenteDuMois { Mois = "avril", Montants = new List<int> { 11, 55, 400 } },
new VenteDuMois { Mois = "mai", Montants = new List<int> { 311, 117, 90, 180 } },
};
// Requête : meilleurs_mois est de type IEnumerable<object>
var grands_montants =
from vente_du_mois in ventes_par_mois
from montant in vente_du_mois.Montants
where montant >= 200
select new { Name=vente_du_mois.Mois, Montant=montant };
// Exécution de la requête : itération sur les montants plus grands que 200
foreach (string mois in grands_montants)
Console.WriteLine("- {0} => {1}", mois.Name, mois.Montant );
// Affiche les grands montants :
// - janvier => 220
// - janvier => 301
// - mars => 231
// - avril => 400
// - mai => 311