« LINQ » : différence entre les versions
Aucun résumé des modifications |
|||
(66 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
[[Category:CSharp]] | [[Category:CSharp]] | ||
= Enumerable = | = Enumerable = | ||
== Générer un énumérable == | == Générer un énumérable == | ||
<kode lang=csharp> | <kode lang=csharp> | ||
var UnACent = Enumerable.Range( | var UnACent = Enumerable.Range(0, 100); // 0 1 2 ... 99 | ||
var DixFoisSept = Enumerable.Repeat(7, 10); // 7 7 ... 7 | var DixFoisSept = Enumerable.Repeat(7, 10); // 7 7 ... 7 | ||
</kode> | </kode> | ||
Ligne 24 : | Ligne 18 : | ||
</kode> | </kode> | ||
{{info | LinqPad permet d'obtenir l'équivalent SQL d'une requête LINQ.}} | {{info | LinqPad permet d'obtenir l'équivalent SQL d'une requête LINQ.}} | ||
= Local function = | |||
<kode lang='cs'> | |||
static bool myLocalFunc(int x) => x > 10; | |||
var results = myList.Where(myLocalFunc); | |||
</kode> | |||
= [http://msdn.microsoft.com/en-us/vcsharp/aa336746 LINQ to OBJECT] = | = [http://msdn.microsoft.com/en-us/vcsharp/aa336746 LINQ to OBJECT] = | ||
Ligne 61 : | Ligne 62 : | ||
</kode> | </kode> | ||
=== | === Select async === | ||
<kode lang='csharp'> | <kode lang='csharp'> | ||
var result = await Task.WhenAll(myEnumerable.Select(MyMethodAsync)); | |||
// WhenAll extension method | |||
public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source) | public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source) | ||
{ | { | ||
Ligne 70 : | Ligne 74 : | ||
var result = await myEnumerable.Select(MyMethodAsync).WhenAll(); | var result = await myEnumerable.Select(MyMethodAsync).WhenAll(); | ||
// SelectAsync extension method | |||
public static async Task<IEnumerable<TResult>> SelectAsync<TSource, TResult>( | public static async Task<IEnumerable<TResult>> SelectAsync<TSource, TResult>( | ||
this IEnumerable<TSource> source, | this IEnumerable<TSource> source, | ||
Ligne 78 : | Ligne 83 : | ||
var result = await myEnumerable.SelectAsync(MyMethodAsync); | var result = await myEnumerable.SelectAsync(MyMethodAsync); | ||
// System.Linq.Async and System.Interactive.Async (for more operators) | |||
var result = await myEnumerable.ToAsyncEnumerable() | |||
.SelectAwait(async x => await MyMethodAsync(x)) | |||
.ToListAsync(); | |||
</kode> | </kode> | ||
* [https://markheath.net/post/async-enumerable-2 Asynchronous Mapping and Filtering] | |||
== SelectMany == | == SelectMany == | ||
Ligne 95 : | Ligne 106 : | ||
from i in li | from i in li | ||
select i; | select i; | ||
// select the parent in the result | |||
var groups = new List<Group> | |||
{ | |||
new Group | |||
{ | |||
Id = 1, | |||
Users = { new User { Id = 1 }, new User { Id = 2 } } | |||
}, | |||
new Group | |||
{ | |||
Id = 2, | |||
Users = { new User { Id = 3 }, new User { Id = 4 } } | |||
} | |||
}; | |||
var userIds = groups.SelectMany(x => x.Users).Select(x => x.Id).ToArray(); // 1, 2, 3, 4 | |||
var groupAndUserIds = groups.SelectMany( | |||
x => x.Users, | |||
(x, y) => new | |||
{ | |||
groupId = x.Id, | |||
userId = y.Id | |||
}).ToArray(); // (1, 1), (1, 2), (2, 3), (2, 4) | |||
</kode> | </kode> | ||
=== | === SelectMany async === | ||
<kode lang='csharp'> | <kode lang='csharp'> | ||
public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source) | public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source) | ||
Ligne 118 : | Ligne 153 : | ||
== OrderBy == | == OrderBy == | ||
<kode lang="csharp"> | <kode lang="csharp"> | ||
// | // tri 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; | ||
// | // tri 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 | // double tri 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" }; | |||
string[] digits = { "zero", "one", "two", "three", "four", "five", "six", | 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; | ||
var | |||
// OrderBy extension method to sort by string property name and specifying the ascending or descending order | |||
private static IEnumerable<T> OrderBy<T>( | |||
this IEnumerable<T> source, | |||
string propertyName, | |||
bool ascendingOrder) | |||
{ | |||
var propertyDescriptor = TypeDescriptor.GetProperties(typeof(T)) | |||
.Find(propertyName, false); | |||
return ascendingOrder ? | |||
source.OrderBy(x => propertyDescriptor.GetValue(x)) : | |||
source.OrderByDescending(x => propertyDescriptor.GetValue(x)); | |||
} | |||
</kode> | </kode> | ||
Ligne 206 : | Ligne 253 : | ||
// return new { Prop1 = word[0], Prop2 = word[1] }; | // return new { Prop1 = word[0], Prop2 = word[1] }; | ||
} | } | ||
</kode> | |||
=== GroupBy + Sum === | |||
<kode lang='cs'> | |||
expenses.GroupBy(x => x.AccountId) | |||
.ToDictionary(x => x.Key, x => x.Sum(y => y.Amount)); | |||
expenses.GroupBy(x => x.AccountId) | |||
.Select(x => | |||
{ | |||
AccountId = x.Key, | |||
TotalAmounts = x => x.Sum(y => y.Amount) | |||
}); | |||
</kode> | |||
=== Flatten === | |||
<kode lang='cs'> | |||
// IEnumerable<IGrouping<char, <string>> → IEnumerable<char, string> | |||
IEnumerable<char, string> result = words.GroupBy(w => w[0]) | |||
.SelectMany(x => x.Select(y => new { Letter = x.Key, Word = y })); | |||
// { (b, blueberry), (b, banana), (c, chimpanzee), ... } | |||
</kode> | </kode> | ||
Ligne 257 : | Ligne 325 : | ||
// apple | // apple | ||
</kode> | |||
== Chunk == | |||
<kode lang='cs'> | |||
// create 10 chunks from a list from 1 to 100: [1 .. 10], [11 .. 20], [21 .. 30] | |||
IEnumerable<int[]> chunks = Enumerable.Range(1, 100).Chunk(10); | |||
</kode> | </kode> | ||
Ligne 282 : | Ligne 356 : | ||
== [https://msdn.microsoft.com/en-us/library/bb348436(v=vs.110).aspx Distinct] == | == [https://msdn.microsoft.com/en-us/library/bb348436(v=vs.110).aspx Distinct] == | ||
=== Spécifier une propriété de distinction === | === 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.<br> | {{info | MoreLINQ [[MoreLINQ#DistinctBy|DistinctBy]]}} | ||
{{boxx|Distinct}} retourne un {{boxx|IEnumerable<T>}} sans doublons. La notion d'identique est vérifiée via {{boxx|Equals}} et {{boxx|GetHashCode}}.<br> | |||
Cette notion d'identique peut être étendue à une comparaison sur une propriété de l'objet (ex : objet.Nom). | Cette notion d'identique peut être étendue à une comparaison sur une propriété de l'objet (ex : objet.Nom). | ||
<kode lang=csharp> | <kode lang=csharp collapsed> | ||
Personne[] personnes = { | Personne[] personnes = { | ||
new Personne() { Nom = "Nicolas" }, | new Personne() { Nom = "Nicolas" }, | ||
Ligne 291 : | Ligne 366 : | ||
}; | }; | ||
personnes.Distinct() // Nicolas Audrey Nicolas | personnes.Distinct(); // Nicolas Audrey Nicolas | ||
personnes.DistinctBy(personne => personne.Nom) // Nicolas Audrey | personnes.DistinctBy(personne => personne.Nom); // Nicolas Audrey | ||
</kode> | </kode> | ||
Pour cela il faut ajouter une méthode d'extension à {{boxx|IEnumerable<T>}} : | Pour cela il faut ajouter une méthode d'extension à {{boxx|IEnumerable<T>}} : | ||
<kode lang=csharp> | <kode lang=csharp collapsed> | ||
using System.Linq; | using System.Linq; | ||
Ligne 336 : | Ligne 411 : | ||
// premier passage workingSentence = un, next = deux, retour "deux un" | // premier passage workingSentence = un, next = deux, retour "deux un" | ||
// deuxième passage workingSentence = "deux un", next = trois, retour "trois 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); | |||
// concat array of strings into 1 string separated by new line | |||
stringArray.Aggregate(new StringBuilder(), (current, next) => current.AppendLine(next)).ToString(); | |||
</kode> | </kode> | ||
Ligne 349 : | Ligne 430 : | ||
</kode> | </kode> | ||
== [https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.intersect | == [https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.intersect IEquatable] == | ||
# si la classe implémente [https://msdn.microsoft.com/en-us/library/ms131187.aspx IEquatable<T>], [https://msdn.microsoft.com/en-us/library/ms131190.aspx Equals(T)] est utilisé | # si la classe implémente [https://msdn.microsoft.com/en-us/library/ms131187.aspx IEquatable<T>], [https://msdn.microsoft.com/en-us/library/ms131190.aspx Equals(T)] est utilisé | ||
# sinon les surcharges de {{boxx|Equals}} et {{boxx|GetHashCode}} de l'objet sont utilisés | # sinon les surcharges de {{boxx|Equals}} et {{boxx|GetHashCode}} de l'objet sont utilisés | ||
# sinon il est possible de spécifier un {{boxx|EqualityComparer<T>}} | # sinon il est possible de spécifier un {{boxx|EqualityComparer<T>}} | ||
<kode lang='cs'> | <kode lang='cs'> | ||
Ligne 392 : | Ligne 472 : | ||
</kode> | </kode> | ||
== [https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.intersect IEqualityComparer] == | |||
{{info | Générer l'EqualityComparer avec [[Resharper#Generate_code|ReSharper]]}} | |||
<kode lang='cs' collapsed> | <kode lang='cs' collapsed> | ||
public class ProductEqualityComparer : IEqualityComparer<Product> | |||
public class ProductEqualityComparer : | |||
{ | { | ||
public | public bool Equals(Product p1, Product p2) | ||
{ | { | ||
if (ReferenceEquals(null, p1) || ReferenceEquals(null, p2)) | if (ReferenceEquals(null, p1) || ReferenceEquals(null, p2)) | ||
Ligne 409 : | Ligne 489 : | ||
} | } | ||
return | return p1.Prop1 == p2.Prop1 && p1.Prop2 == p2.Prop2; // implement your own equality condition | ||
} | } | ||
public | public int GetHashCode(Product p) | ||
{ | { | ||
if (p == null) | if (p == null) | ||
Ligne 419 : | Ligne 499 : | ||
} | } | ||
// | // HashCode.Combine | ||
return (p. | return HashCode.Combine(p.Prop1, p.Prop2, p.Prop3, anotherEqualityComparer.GetHashCode(p.Prop4)); | ||
// | // with an array | ||
var hash = new HashCode(); | |||
hash.Add(p.Prop1); | |||
foreach (var item in p.Prop2) | |||
hash.Add(item); | |||
return hash.ToHashCode(); | |||
// | // best seed, factor and algo for less collisions | ||
int GetCustomHashCode(int seed, int factor, params int[] values) | |||
{ | |||
int hash = seed; | |||
foreach (int i in values) | |||
{ | |||
hash = (hash * factor) + i; | |||
} | |||
return hash; | |||
} | |||
return GetCustomHashCode(1009, 9176, p.Prop1.GetHashCode(), p.Prop2.GetHashCode(), p.Prop3.GetHashCode()); | |||
// algo fron ReSharper | |||
unchecked | unchecked | ||
{ | { | ||
var hashCode = (p.Prop1 != null ? p.Prop1.GetHashCode() : 0); | var hashCode = 17; // seed | ||
hashCode = (hashCode * 397) ^ (p.Prop1 != null ? p.Prop1.GetHashCode() : 0); | |||
hashCode = (hashCode * 397) ^ (p.Prop2 != null ? p.Prop2.GetHashCode() : 0); | hashCode = (hashCode * 397) ^ (p.Prop2 != null ? p.Prop2.GetHashCode() : 0); | ||
hashCode = (hashCode * 397) ^ (p.Prop3 != null ? p.Prop3.GetHashCode() : 0); | hashCode = (hashCode * 397) ^ (p.Prop3 != null ? p.Prop3.GetHashCode() : 0); | ||
Ligne 436 : | Ligne 533 : | ||
} | } | ||
</kode> | </kode> | ||
* [https://stackoverflow.com/questions/1646807/quick-and-simple-hash-code-combinations Best seed and factor] | |||
== [https://docs.microsoft.com/fr-fr/dotnet/api/system.linq.enumerable.zip Zip] == | |||
Applique une fonction aux éléments de deux séquences pour produire une séquence de résultats. | |||
<kode lang='cs'> | |||
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 | |||
</kode> | |||
== [https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.sequenceequal SequenceEqual] == | |||
Determines whether two sequences are equal. | |||
{{warn | Works well with value type, for reference type, use an {{boxx|[[LINQ#IEqualityComparer|IEqualityComparer]]}}}} | |||
<kode lang='cs'> | |||
int[] s1 = { 1, 2, 3, 4 }; | |||
int[] s2 = { 1, 2, 3, 4 }; | |||
Console.WriteLine(s1.SequenceEqual(s2)); // true | |||
Console.WriteLine(s1.SequenceEqual(s2), new MyTypeEqualityComparer()); | |||
</kode> | |||
== [https://markheath.net/post/async-enumerable-1 IAsyncEnumerable] == | |||
=== Task<IReadOnlyCollection<T>> === | |||
Use {{boxx|Task<IReadOnlyCollection<T>>}} when you can return as it is the result of the async call. | |||
<kode lang='cs'> | |||
private static async Task<IReadOnlyCollection<string>> GetKeysAsync() | |||
{ | |||
await Task.Delay(1000); | |||
return new[] { | |||
"Key1", | |||
"Key2" | |||
}; | |||
} | |||
var keys = await GetKeysAsync(); | |||
</kode> | |||
=== IAsyncEnumerable<T> === | |||
<kode lang='cs'> | |||
private static async IAsyncEnumerable<string> GetKeysAsync() | |||
{ | |||
var keys = await GetKeysAsync(); | |||
// here we have some changes on the result of the async call before the result would be sent. | |||
var keyGroups = keys.GroupBy(x => x.Last()); | |||
foreach (var keyGroup in keyGroups) | |||
{ | |||
yield return $"{keyGroup.Key} - {keyGroup}"; | |||
} | |||
} | |||
// to enumerate an IAsyncEnumerable<T> | |||
await foreach (var key in GetKeysAsync()) | |||
{ } | |||
var keys = await GetKeysAsync().ToListAsync(); | |||
</kode> | |||
== [[MoreLINQ]] == | |||
= [https://docs.microsoft.com/fr-fr/dotnet/api/system.linq.enumerable.join Join] = | |||
Met en corrélation les éléments de deux séquences en fonction des clés qui correspondent. | |||
<kode lang='cs'> | |||
// 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 | |||
{ | |||
UserName = user.Name, | |||
GroupName = group.Name | |||
}); | |||
var query = from u in users | |||
join g in groups | |||
on u.GroupId equals g.Id | |||
select new | |||
{ | |||
UserName = u.Name, | |||
GroupName = g.Name | |||
}; | |||
</kode> | |||
* [[MoreLINQ#Left_.2F_Right_.2F_Full_join|Left / Right / Full join More LINQ]] | |||
= [https://msdn.microsoft.com/en-us/library/bb386969(v=vs.110).aspx LINQ to DataSet] = | = [https://msdn.microsoft.com/en-us/library/bb386969(v=vs.110).aspx LINQ to DataSet] = | ||
Ligne 482 : | Ligne 670 : | ||
= [https://www.tutorialspoint.com/linq/linq_sql.htm LINQ to SQL] = | = [https://www.tutorialspoint.com/linq/linq_sql.htm LINQ to SQL] = | ||
== Links == | |||
* [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/basic-linq-query-operations Basic LINQ Query Operations] | |||
* [[LINQKit#PredicateBuilder|PredicateBuilder]] | |||
== [https://docs.microsoft.com/en-us/dotnet/api/system.linq.iqueryable 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é | |||
== Query examples == | |||
<kode lang='cs'> | |||
// filtering: where and or | |||
from user in context.Users | |||
where user.Age > 30 && (user.Name == "Nicolas" || user.Name == "Guillaume") | |||
select user; | |||
// ordering | |||
from user in context.Users | |||
orderby user.Name ascending, user.Age descending | |||
select user; | |||
// grouping: IEnumerable<IGrouping<int, User>> | |||
from user in context.Users | |||
group user by user.Age into g | |||
order by g.Key | |||
select g; | |||
// joining | |||
from user in context.Users | |||
join group in context.Groups | |||
on user.GroupId equals group.Id | |||
select new { UserName = user.Name, GroupName = group.Name }; | |||
</kode> | |||
== [https://www.tektutorialshub.com/entity-framework/single-singleordefault-first-firstordefault-in-entity-framework Find] == | |||
<kode lang='cs'> | |||
// use find if the condition is part of the primary key | |||
var item = await this.DbContext.Set<Item>().FindAsync(cancellationToken, id); | |||
// return null if no item with the same id is found in the context or in the database | |||
</kode> | |||
== Misc == | |||
# View → Server Explorer → Data Connections → Add Connection | # View → Server Explorer → Data Connections → Add Connection | ||
# Add → Class → Data → LINQ to SQL Classes (fichier *.dbml) | # Add → Class → Data → LINQ to SQL Classes (fichier *.dbml) | ||
Ligne 498 : | Ligne 729 : | ||
// affichage du résultat dans un dataGridView WinForm | // affichage du résultat dans un dataGridView WinForm | ||
dataGridView1.DataSource = query; | dataGridView1.DataSource = query; | ||
</kode> | </kode> | ||
Dernière version du 17 septembre 2024 à 12:47
Enumerable
Générer un énumérable
var UnACent = Enumerable.Range(0, 100); // 0 1 2 ... 99 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. |
Local function
static bool myLocalFunc(int x) => x > 10; var results = myList.Where(myLocalFunc); |
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 |
Select async
var result = await Task.WhenAll(myEnumerable.Select(MyMethodAsync)); // WhenAll extension method public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source) { return Task.WhenAll(source); } var result = await myEnumerable.Select(MyMethodAsync).WhenAll(); // SelectAsync extension method 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); // System.Linq.Async and System.Interactive.Async (for more operators) var result = await myEnumerable.ToAsyncEnumerable() .SelectAwait(async x => await MyMethodAsync(x)) .ToListAsync(); |
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; // select the parent in the result var groups = new List<Group> { new Group { Id = 1, Users = { new User { Id = 1 }, new User { Id = 2 } } }, new Group { Id = 2, Users = { new User { Id = 3 }, new User { Id = 4 } } } }; var userIds = groups.SelectMany(x => x.Users).Select(x => x.Id).ToArray(); // 1, 2, 3, 4 var groupAndUserIds = groups.SelectMany( x => x.Users, (x, y) => new { groupId = x.Id, userId = y.Id }).ToArray(); // (1, 1), (1, 2), (2, 3), (2, 4) |
SelectMany 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
// tri 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; // tri 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 tri 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; // OrderBy extension method to sort by string property name and specifying the ascending or descending order private static IEnumerable<T> OrderBy<T>( this IEnumerable<T> source, string propertyName, bool ascendingOrder) { var propertyDescriptor = TypeDescriptor.GetProperties(typeof(T)) .Find(propertyName, false); return ascendingOrder ? source.OrderBy(x => propertyDescriptor.GetValue(x)) : source.OrderByDescending(x => propertyDescriptor.GetValue(x)); } |
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
expenses.GroupBy(x => x.AccountId) .ToDictionary(x => x.Key, x => x.Sum(y => y.Amount)); expenses.GroupBy(x => x.AccountId) .Select(x => { AccountId = x.Key, TotalAmounts = x => x.Sum(y => y.Amount) }); |
Flatten
// IEnumerable<IGrouping<char, <string>> → IEnumerable<char, string> IEnumerable<char, string> result = words.GroupBy(w => w[0]) .SelectMany(x => x.Select(y => new { Letter = x.Key, Word = y })); // { (b, blueberry), (b, banana), (c, chimpanzee), ... } |
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 |
Chunk
// create 10 chunks from a list from 1 to 100: [1 .. 10], [11 .. 20], [21 .. 30] IEnumerable<int[]> chunks = Enumerable.Range(1, 100).Chunk(10); |
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
MoreLINQ DistinctBy |
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); // concat array of strings into 1 string separated by new line stringArray.Aggregate(new StringBuilder(), (current, next) => current.AppendLine(next)).ToString(); |
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 |
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>
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); } } |
IEqualityComparer
Générer l'EqualityComparer avec ReSharper |
public class ProductEqualityComparer : IEqualityComparer<Product> { public bool Equals(Product p1, Product p2) { if (ReferenceEquals(null, p1) || ReferenceEquals(null, p2)) { return false; } if (ReferenceEquals(p1, p2)) { return true; } return p1.Prop1 == p2.Prop1 && p1.Prop2 == p2.Prop2; // implement your own equality condition } public int GetHashCode(Product p) { if (p == null) { return 0; } // HashCode.Combine return HashCode.Combine(p.Prop1, p.Prop2, p.Prop3, anotherEqualityComparer.GetHashCode(p.Prop4)); // with an array var hash = new HashCode(); hash.Add(p.Prop1); foreach (var item in p.Prop2) hash.Add(item); return hash.ToHashCode(); // best seed, factor and algo for less collisions int GetCustomHashCode(int seed, int factor, params int[] values) { int hash = seed; foreach (int i in values) { hash = (hash * factor) + i; } return hash; } return GetCustomHashCode(1009, 9176, p.Prop1.GetHashCode(), p.Prop2.GetHashCode(), p.Prop3.GetHashCode()); // algo fron ReSharper unchecked { var hashCode = 17; // seed hashCode = (hashCode * 397) ^ (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 |
SequenceEqual
Determines whether two sequences are equal.
Works well with value type, for reference type, use an IEqualityComparer |
int[] s1 = { 1, 2, 3, 4 }; int[] s2 = { 1, 2, 3, 4 }; Console.WriteLine(s1.SequenceEqual(s2)); // true Console.WriteLine(s1.SequenceEqual(s2), new MyTypeEqualityComparer()); |
IAsyncEnumerable
Task<IReadOnlyCollection<T>>
Use Task<IReadOnlyCollection<T>> when you can return as it is the result of the async call.
private static async Task<IReadOnlyCollection<string>> GetKeysAsync() { await Task.Delay(1000); return new[] { "Key1", "Key2" }; } var keys = await GetKeysAsync(); |
IAsyncEnumerable<T>
private static async IAsyncEnumerable<string> GetKeysAsync() { var keys = await GetKeysAsync(); // here we have some changes on the result of the async call before the result would be sent. var keyGroups = keys.GroupBy(x => x.Last()); foreach (var keyGroup in keyGroups) { yield return $"{keyGroup.Key} - {keyGroup}"; } } // to enumerate an IAsyncEnumerable<T> await foreach (var key in GetKeysAsync()) { } var keys = await GetKeysAsync().ToListAsync(); |
MoreLINQ
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 { UserName = user.Name, GroupName = group.Name }); var query = from u in users join g in groups on u.GroupId equals g.Id select new { UserName = u.Name, GroupName = g.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
Links
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é
Query examples
// filtering: where and or from user in context.Users where user.Age > 30 && (user.Name == "Nicolas" || user.Name == "Guillaume") select user; // ordering from user in context.Users orderby user.Name ascending, user.Age descending select user; // grouping: IEnumerable<IGrouping<int, User>> from user in context.Users group user by user.Age into g order by g.Key select g; // joining from user in context.Users join group in context.Groups on user.GroupId equals group.Id select new { UserName = user.Name, GroupName = group.Name }; |
Find
// use find if the condition is part of the primary key var item = await this.DbContext.Set<Item>().FindAsync(cancellationToken, id); // return null if no item with the same id is found in the context or in the database |
Misc
- 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; |
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") }; |