Opérations de projection (C#)
La projection désigne l’opération de transformation d’un objet en une nouvelle forme qui se compose souvent seulement de propriétés utilisées ultérieurement. À l'aide de la projection, vous pouvez créer un nouveau type qui est généré à partir de chaque objet. Vous pouvez projeter une propriété et effectuer une fonction mathématique sur celle-ci. Vous pouvez également projeter l’objet d’origine sans le modifier.
Important
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érateurs de requête standard qui effectuent des opérations de projection sont répertoriées dans la section suivante.
Méthodes
Noms des méthodes | Description | Syntaxe d'expression de requête C# | Informations complémentaires |
---|---|---|---|
Sélectionnez | Projette les valeurs qui sont basées sur une fonction de transformation. | select |
Enumerable.Select Queryable.Select |
SelectMany | Projette les séquences de valeurs qui sont basées sur une fonction de transformation, puis les aplatit en une seule séquence. | Utilisation de plusieurs clauses from |
Enumerable.SelectMany Queryable.SelectMany |
Zip | Produit une séquence de tuples avec des éléments de 2 à 3 séquences spécifiées. | Non applicable. | Enumerable.Zip Queryable.Zip |
Select
L’exemple suivant utilise la clause select
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 clauses from
pour projeter tous les mots 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(phrases => phrases.Split(' '));
foreach (string s in query)
{
Console.WriteLine(s);
}
/* This code produces the following output:
an
apple
a
day
the
quick
brown
fox
*/
La méthode SelectMany
peut également former la combinaison résultant de la mise en correspondance de chaque élément de la première séquence avec chaque élément de la seconde 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 méthodes Zip
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'
Important
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
. Créons 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 méthode Zip
projette un tuple, mais cette fois avec trois éléments.
La troisième surcharge accepte un argument Func<TFirst, TSecond, TResult>
qui agit comme sélecteur de résultats. Vous pouvez projeter une nouvelle séquence résultante à partir des séquences zippé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 aux éléments numbers
et letter
correspondants, ce qui produit une séquence des résultats string
.
Select
contre SelectMany
Les deux clauses Select
et SelectMany
retournent une valeur de résultat (ou des valeurs) à partir des valeurs sources. Select
retourne une seule 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
retourne un résultat global unique qui contient des sous-collections concaténées provenant de chaque valeur source. La fonction de transformation qui est passée comme 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 grande séquence.
Les deux illustrations suivantes montrent en quoi les actions de ces deux méthodes sont différentes d’un point de vue conceptuel. 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 de quelle manière Select
retourne une collection qui a le même nombre d’éléments que la collection source.
Cette illustration montre de quelle façon SelectMany
concatène la séquence intermédiaire de tableaux en une seule valeur de résultat final qui contient chaque valeur de chaque tableau intermédiaire.
Exemple de code
L’exemple suivant compare le comportement de Select
et de SelectMany
. Le code crée un « bouquet » de fleurs en prenant les éléments de chaque liste de noms de fleurs de la collection source. Dans l’exemple suivant, la « valeur unique » que la fonction de transformation Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) utilise est une collection de valeurs. Cet exemple nécessite la boucle foreach
supplémentaire pour énumérer chaque chaîne de 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);
}
}