Freigeben über


Projektionsvorgänge (C#)

Projektion bezieht sich auf den Vorgang der Transformation eines Objekts in eine neue Form, die häufig nur aus diesen eigenschaften besteht, die anschließend verwendet werden. Mithilfe der Projektion können Sie einen neuen Typ erstellen, der aus jedem Objekt erstellt wird. Sie können eine Eigenschaft projizieren und eine mathematische Funktion darauf ausführen. Sie können das ursprüngliche Objekt auch projizieren, ohne es zu ändern.

Von Bedeutung

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 Standardmäßigen Abfrageoperatormethoden, die Projektion ausführen, sind im folgenden Abschnitt aufgeführt.

Methodik

Methodennamen BESCHREIBUNG Syntax des C#-Abfrageausdrucks Mehr Informationen
Auswählen Projiziert Werte, die auf einer Transformationsfunktion 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. Verwenden mehrerer from Klauseln Enumerable.SelectMany
Queryable.SelectMany
Schwirren Erzeugt eine Abfolge von Tupeln 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 aus 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 jedes Wort aus jeder Zeichenfolge in einer 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(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
*/

Die SelectMany-Methode kann auch die Kombination bilden, die jedes Element der ersten Sequenz mit jedem Element der zweiten Sequenz verbindet.

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. Zip Alle Methoden arbeiten an Sequenzen von zwei oder mehr möglicherweise heterogenen Typen. Die ersten beiden Überladungen geben Tupel mit dem entsprechenden Positionstyp aus den angegebenen Sequenzen zurück.

Berücksichtigen 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'];

Verwenden Sie den Enumerable.Zip<TFirst,TSecond>(IEnumerable<TFirst>, IEnumerable<TSecond>) Operator, um diese Sequenzen zusammen zu projizieren:

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'

Von Bedeutung

Die resultierende Sequenz eines ZIP-Vorgangs 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. Erstellen wir eine weitere Sammlung, nämlich emoji:

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

Verwenden Sie den Enumerable.Zip<TFirst,TSecond,TThird>(IEnumerable<TFirst>, IEnumerable<TSecond>, IEnumerable<TThird>) Operator, um diese Sequenzen zusammen zu projizieren:

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, diesmal aber mit drei Elementen.

Die dritte Überladung akzeptiert ein Func<TFirst, TSecond, TResult> Argument, das als Ergebnisauswahl 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)

Bei der vorherigen Zip-Überladung wird die angegebene Funktion auf die entsprechenden Elemente number und letter angewendet, wodurch eine Abfolge von string-Ergebnissen erzeugt wird.

Select im Vergleich zu SelectMany

Die Arbeit von beiden Select und SelectMany besteht darin, einen Ergebniswert (oder Werte) aus Quellwerten zu erzeugen. Select erzeugt einen Ergebniswert für jeden Quellwert. Das Gesamtergebnis ist daher eine Auflistung mit der gleichen Anzahl von Elementen wie die Quellauflistung. Im Gegensatz dazu erzeugt SelectMany ein einzelnes Gesamtergebnis, das verkettete Teilkollektionen aus jedem Quellwert enthält. Die Transformationsfunktion, die als Argument an SelectMany übergeben wird, muss eine aufzählbare Abfolge von Werten für jeden Quellwert zurückgeben. SelectMany verkettet diese aufzählbaren Sequenzen, um eine große Sequenz zu erstellen.

Die folgenden beiden Abbildungen zeigen den konzeptionellen Unterschied zwischen den Aktionen dieser beiden Methoden. Gehen Sie in jedem Fall davon aus, dass die Selektorfunktion (Transformation) das Array von Blumen aus jedem Quellwert auswählt.

Diese Abbildung zeigt, wie Select eine Auflistung zurückgegeben wird, die dieselbe Anzahl von Elementen wie die Quellauflistung aufweist.

Grafik, die die Aktion von Select() zeigt

In dieser Abbildung wird dargestellt, wie SelectMany die Zwischenabfolge von Arrays in einen Endergebniswert verkettet wird, der jeden Wert aus jedem Zwischenarray enthält.

Grafik mit der Aktion von SelectMany()

Codebeispiel

Im folgenden Beispiel wird das Verhalten von Select und SelectMany verglichen. Der Code erstellt ein "Blumenstrauß", indem die Elemente aus jeder Liste der Blumennamen in der Quellsammlung entnommen werden. Im folgenden Beispiel ist der von der Transformationsfunktion Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) verwendete einzelne Wert eine Sammlung von Werten. In diesem Beispiel wird die zusätzliche foreach Schleife benötigt, um jede Zeichenfolge in jeder Teilfolge einzeln aufzuzählen.

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);
    }
}

Siehe auch