Projektionsvorgänge (C#)
„Projektion“ bezeichnet einen Vorgang, bei dem ein Objekt in eine neue Form transformiert wird, die häufig nur aus den Eigenschaften besteht, die anschließend verwendet werden. Mithilfe der Projektion können Sie einen neuen Typ erstellen, der aus den einzelnen Objekten erstellt wird. Sie können eine Eigenschaft projizieren und eine mathematische Funktion für sie ausführen. Sie können auch das ursprüngliche Objekt projizieren, ohne es zu ändern.
Wichtig
In diesen Beispielen wird eine System.Collections.Generic.IEnumerable<T>-Datenquelle verwendet. Datenquellen, die auf System.Linq.IQueryProvider basieren, verwenden System.Linq.IQueryable<T>-Datenquellen und Ausdrucksbaumstrukturen. Ausdrucksbaumstrukturen haben Einschränkungen für die zulässige C#-Syntax. Darüber hinaus kann jede IQueryProvider
-Datenquelle, z. B. EF Core, weitere Einschränkungen erzwingen. Konsultieren Sie die Dokumentation für Ihre Datenquelle.
Die Methoden des Standardabfrageoperators, die Projektion ausführen, sind im folgenden Abschnitt aufgeführt.
Methoden
Methodennamen | BESCHREIBUNG | C#-Abfrageausdruckssyntax | Weitere Informationen |
---|---|---|---|
Auswählen | Projektwerte, die auf einer Transform-Funktion basieren. | select |
Enumerable.Select Queryable.Select |
SelectMany | Projiziert Sequenzen von Werten, die auf einer Transform-Funktion basieren, und fasst diese dann in eine Sequenz zusammen. | Mehrere from -Klauseln verwenden |
Enumerable.SelectMany Queryable.SelectMany |
Zip | Erzeugt eine Tupelsequenz mit Elementen aus 2–3 angegebenen Sequenzen. | Nicht zutreffend | Enumerable.Zip Queryable.Zip |
Select
Im folgenden Beispiel wird die select
-Klausel verwendet, um den ersten Buchstaben jeder Zeichenfolge in einer Liste von Zeichenfolgen zu projizieren.
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
*/
Die entsprechende Abfrage mit Methodensyntax ist im folgenden Code dargestellt:
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
Im folgenden Beispiel werden mehrere from
-Klauseln verwendet, um die einzelnen Wörter aus den einzelnen Zeichenfolgen in eine Liste von Zeichenfolgen zu projizieren.
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
*/
Die entsprechende Abfrage mit Methodensyntax ist im folgenden Code dargestellt:
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
*/
Die SelectMany
-Methode kann auch eine Kombination aus der Übereinstimmung mit jedem Element in der ersten Sequenz mit jedem Element in der zweiten Sequenz bilden:
var query = from number in numbers
from letter in letters
select (number, letter);
foreach (var item in query)
{
Console.WriteLine(item);
}
Die entsprechende Abfrage mit Methodensyntax ist im folgenden Code dargestellt:
var method = numbers
.SelectMany(number => letters,
(number, letter) => (number, letter));
foreach (var item in method)
{
Console.WriteLine(item);
}
Zip
Für den Zip
-Projektionsoperator gibt es mehrere Überladungen. Alle Zip
-Methoden werden auf Sequenzen aus mindestens zwei möglicherweise heterogenen Typen angewendet. Die ersten beiden Überladungen geben Tupel mit dem entsprechenden Positionstyp aus den angegebenen Sequenzen zurück.
Betrachten Sie die folgenden Sammlungen:
// 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'];
Um diese Sequenzen zusammen zu projizieren, verwenden Sie den Operator 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'
Wichtig
Die aus einem ZIP-Vorgang resultierende Sequenz ist nie länger als die kürzeste Sequenz. Die Sammlungen numbers
und letters
unterscheiden sich in der Länge. In der resultierenden Sequenz wird das letzte Element aus der Sammlung numbers
ausgelassen, weil ihm eine Entsprechung fehlt.
Die zweite Überladung akzeptiert eine third
-Sequenz. Wir erstellen eine weitere Sammlung namens emoji
:
// A string array with 8 elements.
IEnumerable<string> emoji = [ "🤓", "🔥", "🎉", "👀", "⭐", "💜", "✔", "💯"];
Um diese Sequenzen zusammen zu projizieren, verwenden Sie den Operator 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: 💜
Ähnlich wie bei der vorherigen Überladung projiziert die Zip
-Methode ein Tupel, dieses Mal jedoch mit drei Elementen.
Die dritte Überladung akzeptiert ein Func<TFirst, TSecond, TResult>
-Argument, das als Ergebnisselektor fungiert. Sie können eine neue resultierende Sequenz aus den gezippten Sequenzen projizieren.
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)
Mit der obigen Zip
-Überladung wird die angegebene Funktion auf die entsprechenden Elemente numbers
und letter
angewendet, wodurch eine Sequenz der string
-Ergebnisse erzeugt wird.
Select
im Vergleich mit SelectMany
Die Arbeit von jeweils Select
und SelectMany
besteht darin, einen Ergebniswert (oder Werte) aus den Quellwerten zu erstellen. Select
generiert einen Ergebniswert für jeden Quellwert. Das Ergebnis ist daher eine Auflistung, die über die gleiche Anzahl von Elementen wie die Quellauflistung verfügt. Im Gegensatz dazu erzeugt SelectMany
ein einziges Gesamtergebnis, das verkettete untergeordnete Auflistungen aus jedem Quellwert enthält. Die Transform-Funktion, die als Argument an SelectMany
übergeben wird, muss eine aufzählbare Sequenz von Werten für jeden Quellwert zurückgeben. SelectMany
verkettet diese enumerierbaren Sequenzen, um eine große Sequenz zu erstellen.
Die folgenden zwei Abbildungen zeigen den konzeptionellen Unterschied zwischen den Aktionen der beiden Methoden. In jedem Fall wird davon ausgegangen, dass die Auswahlfunktion (Transform) das Array von Blumen aus jedem Quellwert auswählt.
Die Abbildung zeigt, wie Select
eine Auflistung zurückgibt, die über die gleiche Anzahl von Elementen wie die Quellauflistung verfügt.
Diese Abbildung zeigt, wie SelectMany
die Zwischenmodus-Sequenz von Arrays in einem Endergebniswert verkettet, der jeden Wert aus jedem Zwischenmodus-Array enthält.
Codebeispiel
Im folgenden Beispiel wird das Verhalten von Select
und SelectMany
verglichen. Der Code erstellt anhand der Elemente aus jeder Liste von Blumennamen in der Quellauflistung einen „Blumenstrauß“. Im folgenden Beispiel ist der „einzelne Wert“, den die Transformationsfunktion Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) verwendet, eine Auflistung von Werten. Dieses Beispiel erfordert die zusätzliche foreach
-Schleife, um jede Zeichenfolge in den einzelnen Untersequenzen aufzulisten.
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);
}
}