« LINQ » : différence entre les versions

De Banane Atomic
Aller à la navigationAller à la recherche
 
(62 versions intermédiaires par le même utilisateur non affichées)
Ligne 1 : Ligne 1 :
[[Category:CSharp]]
[[Category:CSharp]]
= Assemblage =
<kode lang=csharp>
// Ajoutez au projet une référence à l'assemblage System.Core
using System.Linq;
</kode>
= Enumerable =
= Enumerable =
== Générer un énumérable ==
== Générer un énumérable ==
<kode lang=csharp>
<kode lang=csharp>
var UnACent = Enumerable.Range(1, 100); // 1 2 ... 100
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>


=== Async ===
=== 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>


=== Async ===
=== 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">
// trie croissant suivant la longueur des mots
// 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;
var sortedWords = words.OrderBy(w => w.Length);


// trie décroissant
// 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;
var sortedDoubles = doubles.OrderByDescending(d => d);


// double trie suivant la taille des mots,  
// double tri 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", "seven", "eight", "nine" };
string[] digits = { "zero", "one", "two", "three", "four", "five", "six",
var sortedDigits = digits.OrderBy(d => d.Length).ThenBy(d => d);
                    "seven", "eight", "nine" };
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 sortedDigits = digits.OrderBy(d => d.Length).ThenBy(d => 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));
}
</kode>
</kode>


Ligne 210 : Ligne 257 :
=== GroupBy + Sum ===
=== GroupBy + Sum ===
<kode lang='cs'>
<kode lang='cs'>
var expensesSumByAccountId = expenses.GroupBy(x => x.AccountId)
expenses.GroupBy(x => x.AccountId)
                                    .ToDictionary(x => x.Key, x => x.Sum(y => y.Amount));
        .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 263 : 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 288 : 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 297 : 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 342 : 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 355 : Ligne 430 :
</kode>
</kode>


== [https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.intersect IEqualityComparer - IEquatable] ==
== [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>}}
{{info | Générer l'EqualityComparer avec [[Resharper#Generate_code|ReSharper]]}}


<kode lang='cs'>
<kode lang='cs'>
Ligne 398 : 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>
// GetHash code est appelé en premier
public class ProductEqualityComparer : IEqualityComparer<Product>
// Si les hash codes sont égaux alors Equals est appelé
public class ProductEqualityComparer : EqualityComparer<Product>
{
{
     public override bool Equals(Product p1, Product p2)
     public bool Equals(Product p1, Product p2)
     {
     {
         if (ReferenceEquals(null, p1) || ReferenceEquals(null, p2))
         if (ReferenceEquals(null, p1) || ReferenceEquals(null, p2))
Ligne 415 : Ligne 489 :
         }
         }


         return Equals(p1.Name, p2.Name); // && Equals(p1.Prop2, p2.Prop2);
         return p1.Prop1 == p2.Prop1 && p1.Prop2 == p2.Prop2; // implement your own equality condition
     }
     }


     public override int GetHashCode(Product p)
     public int GetHashCode(Product p)
     {
     {
         if (p == null)
         if (p == null)
Ligne 425 : Ligne 499 :
         }
         }


         // Get hash code for the Name field if it is not null.  
         // HashCode.Combine
         return (p.Name != null ? p.Name.GetHashCode() : 0);
         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();


         // Calcul du hash code avec plusieurs propriétés
         // best seed, factor and algo for less collisions
         return p.Prop1.GetHashCode() ^ p.Prop2.GetHashCode();
        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());


         // Calcul du hash code avec plusieurs propriétés
         // 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 442 : 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] ==
== [https://docs.microsoft.com/fr-fr/dotnet/api/system.linq.enumerable.zip Zip] ==
Ligne 454 : Ligne 547 :
// 3 three
// 3 three
</kode>
</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] =
= [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.
Met en corrélation les éléments de deux séquences en fonction des clés qui correspondent.
<kode lang='cs'>
<kode lang='cs'>
// Create a list of User-Group pairs where each element is an anonymous type that contains a user name and its belonging group name.
// 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,
var query = users.Join(groups,
                       user => user.GroupId,
                       user => user.GroupId,
                       group => group.Id,
                       group => group.Id,
                       (user, group) =>
                       (user, group) =>
                           new { User = user.Name, GroupName = group.Name });
                           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>
</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 511 : 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 528 : Ligne 730 :
dataGridView1.DataSource = query;
dataGridView1.DataSource = query;
</kode>
</kode>
== [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é
== Group By ==
<kode lang='csharp'>
// 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)
    };
</kode>
== [http://www.albahari.com/nutshell/predicatebuilder.aspx PredicateBuilder] ==
<kode lang='cs'>
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);
</kode>
<filebox fn='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);
    }
}
</filebox>


= LINQ to XML =
= LINQ to XML =

Dernière version du 17 septembre 2024 à 12:47

Enumerable

Générer un énumérable

Csharp.svg
var UnACent = Enumerable.Range(0, 100); // 0 1 2 ... 99
var DixFoisSept = Enumerable.Repeat(7, 10); // 7 7 ... 7

Retourner un énumérable vide

Csharp.svg
return Enumerable.Empty<string>();

LinqPad

Csharp.svg
// afficher le contenu de MyTable
MyTable.Dump();
LinqPad permet d'obtenir l'équivalent SQL d'une requête LINQ.

Local function

Cs.svg
static bool myLocalFunc(int x) => x > 10;

var results = myList.Where(myLocalFunc);

LINQ to OBJECT

Union – Intersect – Except

Csharp.svg
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

Csharp.svg
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

Csharp.svg
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.

Cs.svg
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

Csharp.svg
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

Csharp.svg
// 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>.

Csharp.svg
// 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é

Csharp.svg
// 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

Cs.svg
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

Cs.svg
// 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>>

Csharp.svg
// 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

Cs.svg
// 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

Csharp.svg
// 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).

Csharp.svg
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> :

Csharp.svg
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

Csharp.svg
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

Csharp.svg
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

  1. si la classe implémente IEquatable<T>, Equals(T) est utilisé
  2. sinon les surcharges de Equals et GetHashCode de l'objet sont utilisés
  3. sinon il est possible de spécifier un EqualityComparer<T>
Cs.svg
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
Cs.svg
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
Cs.svg
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.

Cs.svg
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
Cs.svg
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.

Cs.svg
private static async Task<IReadOnlyCollection<string>> GetKeysAsync()
{
    await Task.Delay(1000);

    return new[] {
        "Key1",
        "Key2"
    };
}

var keys = await GetKeysAsync();

IAsyncEnumerable<T>

Cs.svg
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.

Cs.svg
// 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

Csharp.svg
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

Xaml.svg
<DataGrid ItemsSource="{Binding Result}" />
Csharp.svg
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

Cs.svg
// 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

Cs.svg
// 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

  1. View → Server Explorer → Data Connections → Add Connection
  2. Add → Class → Data → LINQ to SQL Classes (fichier *.dbml)
  3. Glisser les tables dans le designer
Csharp.svg
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.

Csharp.svg
// 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

Csharp.svg
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);

Doc XPath

Écriture

Csharp.svg
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.svg
<?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

Csharp.svg
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é.
Csharp.svg
// 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

Csharp.svg
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")
            };