Partager via


Opérations de projection (C#)

La projection fait référence à l’opération de transformation d’un objet en une nouvelle forme qui se compose souvent de ces propriétés utilisées par la suite. À l’aide de la projection, vous pouvez construire un nouveau type généré à partir de chaque objet. Vous pouvez projeter une propriété et y effectuer une fonction mathématique. Vous pouvez également projeter l’objet d’origine sans le modifier.

Importante

Ces exemples utilisent une source de données System.Collections.Generic.IEnumerable<T>. Les sources de données basées sur System.Linq.IQueryProvider utilisent des sources de données System.Linq.IQueryable<T> et des arborescences d’expressions. Les arborescences d’expressions présentent des limitations sur la syntaxe C# autorisée. De plus, chaque source de données IQueryProvider, telle que EF Core peut imposer des restrictions supplémentaires. Consultez la documentation de votre source de données.

Les méthodes d’opérateur de requête standard qui effectuent une projection sont répertoriées dans la section suivante.

Méthodes

Noms de méthode Descriptif Syntaxe d’expression de requête C# Plus d’informations
Sélectionnez Projette des valeurs qui sont basées sur une fonction de transformation. select Enumerable.Select
Queryable.Select
SelectMany Projette des séquences de valeurs basées sur une fonction de transformation, puis les aplatit en une seule séquence. Utiliser plusieurs from clauses Enumerable.SelectMany
Queryable.SelectMany
Code postal Produit une séquence de tuples dont les éléments proviennent de 2 ou 3 séquences spécifiées. Non applicable. Enumerable.Zip
Queryable.Zip

Select

L’exemple suivant utilise la select clause pour projeter la première lettre de chaque chaîne dans une liste de chaînes.

List<string> words = ["an", "apple", "a", "day"];

var query = from word in words
            select word.Substring(0, 1);

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    a
    a
    a
    d
*/

La requête équivalente utilisant la syntaxe de méthode est illustrée dans le code suivant :

List<string> words = ["an", "apple", "a", "day"];

var query = words.Select(word => word.Substring(0, 1));

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    a
    a
    a
    d
*/

SelectMany

L’exemple suivant utilise plusieurs from clauses pour projeter chaque mot de chaque chaîne dans une liste de chaînes.

List<string> phrases = ["an apple a day", "the quick brown fox"];

var query = from phrase in phrases
            from word in phrase.Split(' ')
            select word;

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    an
    apple
    a
    day
    the
    quick
    brown
    fox
*/

La requête équivalente utilisant la syntaxe de méthode est illustrée dans le code suivant :

List<string> phrases = ["an apple a day", "the quick brown fox"];

var query = phrases.SelectMany(phrase => phrase.Split(' '));

foreach (string s in query)
{
    Console.WriteLine(s);
}

/* This code produces the following output:

    an
    apple
    a
    day
    the
    quick
    brown
    fox
*/

La SelectMany méthode peut également former la combinaison de correspondances entre chaque élément de la première séquence et chaque élément de la deuxième séquence :

var query = from number in numbers
            from letter in letters
            select (number, letter);

foreach (var item in query)
{
    Console.WriteLine(item);
}

La requête équivalente utilisant la syntaxe de méthode est illustrée dans le code suivant :

var method = numbers
    .SelectMany(number => letters,
    (number, letter) => (number, letter));

foreach (var item in method)
{
    Console.WriteLine(item);
}

Zip

Il existe plusieurs surcharges pour l’opérateur de projection Zip. Toutes les Zip méthodes fonctionnent sur des séquences de deux types ou plus potentiellement hétérogènes. Les deux premières surcharges retournent des tuples, avec le type positionnel correspondant à partir des séquences données.

Tenez compte des collections suivantes :

// An int array with 7 elements.
IEnumerable<int> numbers = [1, 2, 3, 4, 5, 6, 7];
// A char array with 6 elements.
IEnumerable<char> letters = ['A', 'B', 'C', 'D', 'E', 'F'];

Pour projeter ces séquences ensemble, utilisez l’opérateur Enumerable.Zip<TFirst,TSecond>(IEnumerable<TFirst>, IEnumerable<TSecond>) :

foreach ((int number, char letter) in numbers.Zip(letters))
{
    Console.WriteLine($"Number: {number} zipped with letter: '{letter}'");
}
// This code produces the following output:
//     Number: 1 zipped with letter: 'A'
//     Number: 2 zipped with letter: 'B'
//     Number: 3 zipped with letter: 'C'
//     Number: 4 zipped with letter: 'D'
//     Number: 5 zipped with letter: 'E'
//     Number: 6 zipped with letter: 'F'

Importante

La séquence résultante d’une opération zip n’est jamais plus longue que la séquence la plus courte. Les collections numbers et letters diffèrent par leur longueur, et la séquence qui en résulte omet le dernier élément de la collection numbers, car il n’a rien à compresser.

La deuxième surcharge accepte une séquence third. Nous allons créer une autre collection, à savoir emoji:

// A string array with 8 elements.
IEnumerable<string> emoji = [ "🤓", "🔥", "🎉", "👀", "⭐", "💜", "✔", "💯"];

Pour projeter ces séquences ensemble, utilisez l’opérateur Enumerable.Zip<TFirst,TSecond,TThird>(IEnumerable<TFirst>, IEnumerable<TSecond>, IEnumerable<TThird>) :

foreach ((int number, char letter, string em) in numbers.Zip(letters, emoji))
{
    Console.WriteLine(
        $"Number: {number} is zipped with letter: '{letter}' and emoji: {em}");
}
// This code produces the following output:
//     Number: 1 is zipped with letter: 'A' and emoji: 🤓
//     Number: 2 is zipped with letter: 'B' and emoji: 🔥
//     Number: 3 is zipped with letter: 'C' and emoji: 🎉
//     Number: 4 is zipped with letter: 'D' and emoji: 👀
//     Number: 5 is zipped with letter: 'E' and emoji: ⭐
//     Number: 6 is zipped with letter: 'F' and emoji: 💜

Tout comme la surcharge précédente, la Zip méthode projette un tuple, mais cette fois avec trois éléments.

La troisième surcharge accepte un Func<TFirst, TSecond, TResult> argument qui agit comme sélecteur de résultats. Vous pouvez projeter une nouvelle séquence résultante à partir des séquences compressées.

foreach (string result in
    numbers.Zip(letters, (number, letter) => $"{number} = {letter} ({(int)letter})"))
{
    Console.WriteLine(result);
}
// This code produces the following output:
//     1 = A (65)
//     2 = B (66)
//     3 = C (67)
//     4 = D (68)
//     5 = E (69)
//     6 = F (70)

Avec la surcharge précédente Zip, la fonction spécifiée est appliquée sur les éléments number et letter, ce qui génère une séquence des résultats string.

Select contre SelectMany

Le travail des deux Select et SelectMany consiste à produire une valeur de résultat (ou des valeurs) à partir de valeurs sources. Select produit une valeur de résultat pour chaque valeur source. Le résultat global est donc une collection qui a le même nombre d’éléments que la collection source. En revanche, SelectMany produit un résultat global unique qui contient des sous-collections concaténées à partir de chaque valeur source. La fonction de transformation qui est passée en tant qu'argument à SelectMany doit retourner une séquence énumérable de valeurs pour chaque valeur source. SelectMany concatène ces séquences énumérables pour créer une séquence volumineuse.

Les deux illustrations suivantes montrent la différence conceptuelle entre les actions de ces deux méthodes. Dans chaque cas, supposons que la fonction (de transformation) du sélecteur sélectionne le tableau de fleurs (Flowers) à partir de chaque valeur source.

Cette illustration montre comment Select retourne une collection qui a le même nombre d’éléments que la collection source.

Graphique montrant l’action Select()

Cette illustration montre comment SelectMany concaténer la séquence intermédiaire de tableaux en une valeur de résultat finale qui contient chaque valeur de chaque tableau intermédiaire.

Graphique montrant l’action de SelectMany()

Exemple de code

L’exemple suivant compare le comportement de Select et SelectMany. Le code crée un « bouquet » de fleurs en prenant les éléments de chaque liste de noms de fleurs dans la collection source. Dans l’exemple suivant, la « valeur unique » utilisée par la fonction Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) de transformation est une collection de valeurs. Cet exemple nécessite la boucle supplémentaire foreach afin d’énumérer chaque chaîne dans chaque sous-séquence.

class Bouquet
{
    public required List<string> Flowers { get; init; }
}

static void SelectVsSelectMany()
{
    List<Bouquet> bouquets =
    [
        new Bouquet { Flowers = ["sunflower", "daisy", "daffodil", "larkspur"] },
        new Bouquet { Flowers = ["tulip", "rose", "orchid"] },
        new Bouquet { Flowers = ["gladiolis", "lily", "snapdragon", "aster", "protea"] },
        new Bouquet { Flowers = ["larkspur", "lilac", "iris", "dahlia"] }
    ];

    IEnumerable<List<string>> query1 = bouquets.Select(bq => bq.Flowers);

    IEnumerable<string> query2 = bouquets.SelectMany(bq => bq.Flowers);

    Console.WriteLine("Results by using Select():");
    // Note the extra foreach loop here.
    foreach (IEnumerable<string> collection in query1)
    {
        foreach (string item in collection)
        {
            Console.WriteLine(item);
        }
    }

    Console.WriteLine("\nResults by using SelectMany():");
    foreach (string item in query2)
    {
        Console.WriteLine(item);
    }
}

Voir aussi