« LINQ » : différence entre les versions
Ligne 120 : | Ligne 120 : | ||
// trie croissant suivant la longueur des mots | // trie croissant suivant la longueur des mots | ||
string[] words = { "cherry", "apple", "blueberry" }; | string[] words = { "cherry", "apple", "blueberry" }; | ||
var sortedWords = words.OrderBy(w => w.Length); | |||
var sortedWords = from w in words | var sortedWords = from w in words | ||
orderby w.Length | orderby w.Length | ||
select w; | select w; | ||
// trie décroissant | // trie décroissant | ||
double[] doubles = { 1.7, 2.3, 1.9, 4.1, 2.9 }; | double[] doubles = { 1.7, 2.3, 1.9, 4.1, 2.9 }; | ||
var sortedDoubles = doubles.OrderByDescending(d => d); | |||
var sortedDoubles = from d in doubles | var sortedDoubles = from d in doubles | ||
orderby d descending | orderby d descending | ||
select d; | select d; | ||
// double trie suivant la taille des mots, | // double trie suivant la taille des mots, | ||
// et en cas d'égalité suivant l'ordre alphabétique | // et en cas d'égalité suivant l'ordre alphabétique | ||
string[] digits = { "zero", "one", "two", "three", "four", "five", "six", | string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; | ||
var sortedDigits = digits.OrderBy(d => d.Length).ThenBy(d => d); | |||
var sortedDigits = from d in digits | var sortedDigits = from d in digits | ||
orderby d.Length, d | orderby d.Length, d | ||
select d; | select d; | ||
</kode> | </kode> | ||
Version du 9 novembre 2020 à 21:03
Assemblage
// Ajoutez au projet une référence à l'assemblage System.Core using System.Linq; |
Enumerable
Générer un énumérable
var UnACent = Enumerable.Range(1, 100); // 1 2 ... 100 var DixFoisSept = Enumerable.Repeat(7, 10); // 7 7 ... 7 |
Retourner un énumérable vide
return Enumerable.Empty<string>(); |
LinqPad
// afficher le contenu de MyTable MyTable.Dump(); |
LinqPad permet d'obtenir l'équivalent SQL d'une requête LINQ. |
LINQ to OBJECT
Union – Intersect – Except
int[] numbersA = { 0, 1, 2, 3 }; int[] numbersB = { 0, 2, 4, 6 }; // liste des numéros des deux tableaux IEnumerable<int> allNumbers = numbersA.Concat(numbersB); // 0 1 2 3 0 2 4 6 // liste des numéros des deux tableaux sans les doublons IEnumerable<int> uniqueNumbers = numbersA.Union(numbersB); // 0 1 2 3 4 6 // liste des numéros présent dans les deux tableaux IEnumerable<int> duplicateNumbers = numbersA.Intersect(numbersB); // 0 2 // équivalent Comprehension Query Syntax IEnumerable<int> query = from itemA in numbersA join itemB in numbersB on itemA equals itemB select itemA; // liste des numéros présent dans numbersA mais pas dans numbersB IEnumerable<int> exceptNumbers = numbersA.Except(numbersB); // 1 3 |
Surcharger la comparaison avec IEquatable ou EqualityComparer |
Select
int[] numbers = { 1, 2, 3 }; var numbersPlusOne = from n in numbers select n + 1; var numbersPlusOne = numbers.Select(n => n + 1); // numbersPlusOne : 2 3 4 |
Async
public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source) { return Task.WhenAll(source); } var result = await myEnumerable.Select(MyMethodAsync).WhenAll(); public static async Task<IEnumerable<TResult>> SelectAsync<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, Task<TResult>> func) { return await Task.WhenAll(source.Select(func)); } var result = await myEnumerable.SelectAsync(MyMethodAsync); |
SelectMany
Même chose que Select, mais aplatit les résultat en une seule énumération.
var numbers = new List<List<int>>() { new List<int>() { 1, 2, 3 }, new List<int>() { 4, 5, 6 } }; // List<List<int>> → IEnumerable<List<int>> var numbersSelect = numbers.Select(li => li); // List<ListInt> → IEnumerable<int>, le résultat est aplatit en une seule énumération: [1, 2, 3, 4, 5, 6] var numbersSelectMany = numbers.SelectMany(li => li); var numbersSelectMany = from li in numbers from i in li select i; |
Async
public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source) { return Task.WhenAll(source); } var result = (await myEnumerable.Select(MyMethodAsync).WhenAll()).SelectMany(s => s); public static async Task<IEnumerable<TResult>> SelectManyAsync<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, Task<IEnumerable<TResult>>> func) { return (await Task.WhenAll(source.Select(func))).SelectMany(s => s); } var result = await myEnumerable.SelectManyAsync(MyMethodAsync); |
OrderBy
// trie croissant suivant la longueur des mots string[] words = { "cherry", "apple", "blueberry" }; var sortedWords = words.OrderBy(w => w.Length); var sortedWords = from w in words orderby w.Length select w; // trie décroissant double[] doubles = { 1.7, 2.3, 1.9, 4.1, 2.9 }; var sortedDoubles = doubles.OrderByDescending(d => d); var sortedDoubles = from d in doubles orderby d descending select d; // double trie suivant la taille des mots, // et en cas d'égalité suivant l'ordre alphabétique string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; var sortedDigits = digits.OrderBy(d => d.Length).ThenBy(d => d); var sortedDigits = from d in digits orderby d.Length, d select d; |
GroupBy
Permet de regrouper suivant une clé. Retourne une liste de IGrouping contenant chacun la valeur de la clé et la liste des éléments correspondant à la clé.
IGrouping<out TKey, out TElement> hérite de IEnumerable<TElement>.
// groupement des mots suivant leur première lettre string[] words = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese" }; var wordGroups = from w in words group w by w[0] into g; var wordGroups = words.GroupBy(w => w[0]); foreach (var g in wordGroups) { Console.WriteLine("Words that start with the letter '{0}':", g.Key); // g implémente IEnumerable<string> foreach (var w in g) { Console.WriteLine(w); } } // Result // Words that start with the letter 'b': // blueberry // banana // Words that start with the letter 'c': // chimpanzee // cheese // Words that start with the letter 'a': // abacus // apple // Variante var wordGroups = from w in words group w by w[0] into g select new { FirstLetter = g.Key, Words = g }; var wordGroups = words.GroupBy(w => w[0]) .Select(g => new { FirstLetter = g.Key, Words = g }); foreach (var g in wordGroups) { Console.WriteLine("Words that start with the letter '{0}':", g.FirstLetter); foreach (var w in g.Words) { Console.WriteLine(w); } } // Group by sur plusieurs éléments var groupement= maListe.GroupBy(element => new {element.Propriété1, element.Propriété2}); |
Avec un delegate nommé
// avec un delagate anonyme var wordGroups = words.GroupBy(w => w[0]); // avec un delegate nommé var wordGroups = words.GroupBy(GroupByDelegate); private char GroupByDelegate(string word) { return word[0]; // le type de retour est object dans le cas où return renvoie un type anonyme // return new { Prop1 = word[0], Prop2 = word[1] }; } |
GroupBy + Sum
var expensesSumByAccountId = expenses.GroupBy(x => x.AccountId) .ToDictionary(x => x.Key, x => x.Sum(y => y.Amount)); |
Lookup
Lookup est executé immediatement, à la différence de GroupBy qui est exécuté en différé. Il vaut mieux utiliser Lookup si l'on souhaite parcourir le résultat plus d'une fois.
ILookup<TKey, TElement> hérite de IEnumerable<IGrouping<TKey, TElement>> et se comporte comme un IDictionary<TKey, IEnumerable<TElement>>
// groupement des mots suivant leur première lettre string[] words = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese" }; // GroupBy IEnumerable<IGrouping<char, string>> wordGroupsGroupBy = words.GroupBy(w => w[0]); foreach (IGrouping<char, string> g in wordGroupsGroupBy) { Console.WriteLine("Words that start with the letter '{0}':", g.Key); foreach (var w in g) { Console.WriteLine(w); } } // Lookup ILookup<char, string> wordGroupsLookup = words.ToLookup(w => w[0]); foreach (IGrouping<char, string> g in wordGroupsLookup) { Console.WriteLine("Words that start with the letter '{0}':", g.Key); foreach (var w in g) { Console.WriteLine(w); } } // Même résultats avec GroupBy et Lookup // Words that start with the letter 'b': // blueberry // banana // Words that start with the letter 'c': // chimpanzee // cheese // Words that start with the letter 'a': // abacus // apple // possibilité d'utiliser ILookup comme un dictionnaire foreach (string w in wordGroupsLookup['a']) { Console.WriteLine(w); } // Resultats // abacus // apple |
Any – All
// Test si au moins un des éléments valide le critère. string[] words = { "pomme", "fraise", "kiwi", }; bool isWordWithW = words.Any(w => w.Contains("w")); // true // Test si tous les éléments valident le critère. int[] numbers = { 1, 11, 3, 19, 41, 65, 19 }; bool onlyOdd = numbers.All(n => n % 2 == 1); // true, tous impair // attention, si l'énumération est vide, All retourne true var numbers = Enumerable.Empty<int>(); bool onlyOdd = numbers.All(n => n % 2 == 1); // true bool onlyOdd = numbers.DefaultIfEmpty().All(n => n % 2 == 1); // false // numbers.DefaultIfEmpty() retourne un enumerable avec un élément de valeur default, ici 0 pour int // numbers.DefaultIfEmpty(-1) on peut aussi forcer la valeur // Any permet aussi de tester si une énumération contient des éléments ou non var numbers = new List<int> { 1, 2 }; bool hasElements = numbers.Any(); |
Distinct
Spécifier une propriété de distinction
Distinct retourne un IEnumerable<T> sans doublons. La notion d'identique est vérifiée via Equals et GetHashCode.
Cette notion d'identique peut être étendue à une comparaison sur une propriété de l'objet (ex : objet.Nom).
Personne[] personnes = { new Personne() { Nom = "Nicolas" }, new Personne() { Nom = "Audrey" }, new Personne() { Nom = "Nicolas" } }; personnes.Distinct() // Nicolas Audrey Nicolas personnes.DistinctBy(personne => personne.Nom) // Nicolas Audrey |
Pour cela il faut ajouter une méthode d'extension à IEnumerable<T> :
using System.Linq; public static class IEnumerableExtensions { public static IEnumerable<T> DistinctBy<T>(this IEnumerable<T> source, Func<T, object> uniqueCheckerMethod) { return source.Distinct(new GenericComparer<T>(uniqueCheckerMethod)); } } class GenericComparer<T> : IEqualityComparer<T> { private Func<T, object> _uniqueCheckerMethod; public GenericComparer(Func<T, object> uniqueCheckerMethod) { this._uniqueCheckerMethod = uniqueCheckerMethod; } bool IEqualityComparer<T>.Equals(T x, T y) { return this._uniqueCheckerMethod(x).Equals(this._uniqueCheckerMethod(y)); } int IEqualityComparer<T>.GetHashCode(T obj) { return this._uniqueCheckerMethod(obj).GetHashCode(); } } |
Aggregate
string sentence = "un deux trois quatre cinq"; string[] words = sentence.Split(' '); string reversed = words.Aggregate((workingSentence, next) => next + " " + workingSentence); // premier passage workingSentence = un, next = deux, retour "deux un" // deuxième passage workingSentence = "deux un", next = trois, retour "trois deux un" // sum with seed of 0 in case of intArray is empty to avoid "sequence contains no elements" exception var sum = intArray.Aggregate(0, (current, next) => current + next); |
Take / Skip
int[] chiffres = { 1, 2, 3, 4 }; // saute les 2 premiers éléments et retourne le reste chiffres.Skip(2); // 3 4 // prend seulement les 2 premiers éléments chiffres.Take(2); // 1 2 |
IEqualityComparer - IEquatable
- si la classe implémente IEquatable<T>, Equals(T) est utilisé
- sinon les surcharges de Equals et GetHashCode de l'objet sont utilisés
- sinon il est possible de spécifier un EqualityComparer<T>
Générer l'EqualityComparer avec ReSharper |
var products1 = new Product[] { new Product { Name = "apple" }, new Product { Name = "orange" } }; var products2 = new Product[] { new Product { Name = "apple" }, new Product { Name = "lemon" } }; products1.Intersect(products2); // empty // leur référence est utilisé pour les comparer, ils sont donc tous différents même s'ils ont le même Name product1.Intersect(product2, new ProductEqualityComparer()); // apple |
public class Product : IEquatable<Product> { public string Name { get; set; } public bool Equals(Product other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return string.Equals(Name, other.Name); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((Product) obj); } public override int GetHashCode() { return (Name != null ? Name.GetHashCode() : 0); } } |
// GetHash code est appelé en premier // Si les hash codes sont égaux alors Equals est appelé public class ProductEqualityComparer : EqualityComparer<Product> { public override bool Equals(Product p1, Product p2) { if (ReferenceEquals(null, p1) || ReferenceEquals(null, p2)) { return false; } if (ReferenceEquals(p1, p2)) { return true; } return Equals(p1.Name, p2.Name); // && Equals(p1.Prop2, p2.Prop2); } public override int GetHashCode(Product p) { if (p == null) { return 0; } // Get hash code for the Name field if it is not null. return (p.Name != null ? p.Name.GetHashCode() : 0); // Calcul du hash code avec plusieurs propriétés return p.Prop1.GetHashCode() ^ p.Prop2.GetHashCode(); // Calcul du hash code avec plusieurs propriétés unchecked { var hashCode = (p.Prop1 != null ? p.Prop1.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (p.Prop2 != null ? p.Prop2.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (p.Prop3 != null ? p.Prop3.GetHashCode() : 0); return hashCode; } } } |
Zip
Applique une fonction aux éléments de deux séquences pour produire une séquence de résultats.
int[] numbers = { 1, 2, 3, 4 }; string[] words = { "one", "two", "three" }; IEnumerable<string> numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second); // 1 one // 2 two // 3 three |
Join
Met en corrélation les éléments de deux séquences en fonction des clés qui correspondent.
// join a list of users and a list of groups // pair them arround the matching between user.GroupId and group.Id var query = users.Join(groups, user => user.GroupId, group => group.Id, (user, group) => new { User = user.Name, GroupName = group.Name }); |
LINQ to DataSet
DataTable clients = dataSet.Tables["client"]; DataTable commandes = dataSet.Tables["commande"]; var query = from client in clients.AsEnumerable() join commande in commandes.AsEnumerable() on client.Field<int>("id") equals commande.Field<int>("clientId") where client.Field<string>("name").EndsWith("2") && commande.Field<string>("details").EndsWith("2") select new { ClientId = client.Field<int>("id"), ClientName = client.Field<string>("name"), CommandeId = commande.Field<int>("id"), CommandeDetails = commande.Field<string>("details") }; |
Affichage de la requête dans WPF
<DataGrid ItemsSource="{Binding Result}" /> |
private IEnumerable _result; public IEnumerable Result { get { return _result; } set { _result = value; OnPropertyChanged("Result"); } } /* ... */ Result = query; |
LINQ to SQL
- View → Server Explorer → Data Connections → Add Connection
- Add → Class → Data → LINQ to SQL Classes (fichier *.dbml)
- Glisser les tables dans le designer
var db = new DataClasses1DataContext(); var query = from client in db.clients select new { ClientId = client.id, ClientName = client.name }; // affichage du résultat dans un dataGridView WinForm dataGridView1.DataSource = query; |
IQueryable
Étend IEnumerable, et possède
- Provider: query provider associé au type de données (LINQ to SQL)
- Expression: arbre d'expression
- ElementType: type d'élément retourné
Group By
// groupement des clients par ville var query = from client in db.clients group by client.city into group select new { City = group.Key, ClientNumber = group.Sum(c => c.city) }; |
PredicateBuilder
Expression<Func<Item, bool>> predicate = x => x.Date.Year > 2019; // concat predicates var predicate = PredicateBuilder.True<Item>(); predicate = predicate.And(x => x.Date.Year > 2019); predicate = predicate.And(x => x.Date.Year < 2021); |
PredicateBuilder.cs |
public static class PredicateBuilder { public static Expression<Func<T, bool>> True<T>() => f => true; public static Expression<Func<T, bool>> False<T>() => f => false; public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>()); return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters); } public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>()); return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters); } } |
LINQ to XML
XDocument est disponible à partir du Framework 3.5 |
Lecture
Ne pas fait directement les requêtes sur XDocument car c'est un nœud virtuel qui ne contient que le nœud racine. Utiliser donc XDocument.Root.
// chargement du XML depuis un fichier XDocument xDoc = XDocument.Load(xmlFilePath); // ou depuis une chaîne de caratères XDocument xDoc = XDocument.Parse("<?xml version=\"1.0\"?><root><noeud /></root>"); // Séléctionne tous les noeuds "noeud2" fils direct de "noeud1" // qui lui-même est fils direct du noeud racine. var query = xDoc.Root.Element("noeud1").Elements("noeud2"); var query = from n in xDoc.Root.Element("noeud1").Elements("noeud2") select n; // Séléctionne tous les noeuds "noeud2" fils direct ou non de la racine var query = xDoc.Root.Descendants("noeud2"); // Récupère le contenu de noeud1, <noeud1>true</noeud1> ici "true" au format string string content = xDoc.Root.Element("noeud1").Value; // Convertion bool b = XmlConvert.ToBoolean(content); foreach (var noeud in query) // noeud est un XElement { string attribut = noeud.Attribute("attribut").Value; string attribut = (string)noeud.Attribute("attribut"); // cast de XAttribut } |
XPath
using System.Xml.XPath; var noeud2 = xDoc.XPathSelectElement("/noeud1/noeud2"); var noeud2innerText = noeud2.Value; IEnumerable<XElement> elements = xDoc.XPathSelectElements("noeud1 | noeud2"); IEnumerable attributs = (IEnumerable)xDoc.XPathEvaluate("/noeud/@nom_attribut"); var attribut = attributs.Cast<XAttribute>().FirstOrDefault(); // gestion des namespaces var xmlNamespaceManager = new XmlNamespaceManager(new NameTable()); xmlNamespaceManager.AddNamespace("prefix", "uri"); var noeud2 = xDoc.XPathSelectElement("/noeud1/prefix:noeud2", xmlNamespaceManager); |
Écriture
var xDoc = new XDocument( new XDeclaration("1.0", "utf-8", "yes"), new XComment("Commentaire XML"), new XElement("contacts", new XElement("contact", new XElement("name", "Nicolas"), new XElement("phone", new XAttribute("type", "mobile"), "06-...")), new XElement("contact", new XElement("name", "Audrey"), new XElement("phone", "01-...")))); var xDoc = new XDocument(); xDoc.Declaration = new XDeclaration("1.0", "utf-8", "yes"); xDoc.Add(new XComment("Commentaire XML")); var xContacts = new XElement("contacts"); xDoc.Root.Add(xContacts); var xContactNicolas = new XElement("contact"); xContacts.Add(xContactNicolas); xContactNicolas.SetElementValue("name", "Nicolas"); // équivalent à xContactNicolas.Add(new XElement("name", "Nicolas")); var xPhone = new XElement("phone"); xContactNicolas.Add(xPhone); xPhone.SetAttributeValue("type", "mobile"); // équivalent à xPhone.Add(new XAttribute("type", "mobile")); xPhone.SetValue("06-..."); var xContactAudrey = new XElement("contact"); xContacts.Add(xContactAudrey); xContactAudrey.Add(new XElement("name", "Audrey")); xContactAudrey.Add(new XElement("phone", "01-...")); |
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <!--Commentaire XML --> <contacts> <contact> <name>Nicolas</name> <phone type="mobile">06-...</phone> </contact> <contact> <name>Audrey</name> <phone>01-...</phone> </contact> </contacts> |
XElement.Remove() et foreach
foreach (XElement xElt in xDoc.Root.Elements()) { // PB : après l'appel de Remove, on sort du foreach xElt.Remove(); } // Solution 1 : utiliser LINQ xDoc.Root.Elements().Remove(); // Solution 2 : parcourir à l’envers foreach (XElement xElt in xDoc.Root.Elements().Reverse()) { xElt.Remove(); } // Solution 3 : créer une liste temporaire d'éléments à supprimer |
XElement.SetAttributeValue
- Assigne la valeur à l'attribut spécifié.
- Créer l'attribut s'il n'existe pas.
- Si la valeur est null l'attribut est supprimé.
// code du Framework .NET public void SetAttributeValue(XName name, object value) { XAttribute xAttribute = this.Attribute(name); if (value == null) { if (xAttribute != null) { this.RemoveAttribute(xAttribute); return; } } else { if (xAttribute != null) { xAttribute.Value = XContainer.GetStringValue(value); return; } this.AppendAttribute(new XAttribute(name, value)); } } |
Cast
Nativement les objets XAttribut et XElement peuvent être castés en :
- DateTime, DateTimeOffset, TimeSpan
- Decimal, Double, Int32, UInt32, Int64, UInt64
- Guid
- String
- Boolean
Remarque : pour les XElement c'est la valeur du noeud ou la concaténation des valeurs de ses sous-noeuds qui sera casté.
Jointure
var query = from n1 in xdoc.Root.Elements("noeud1") join n2 in xdoc.Root.Elements("noeud2") on (string)n1.Attribute("attribut") equals (string)n2.Attribute("attribut") select new { Propriété1 = n1.Attribute("p1"), Propriété2 = n2.Attribute("p2") }; |