Operacje projekcji (C#)

Projekcja odnosi się do operacji przekształcania obiektu w nową formę, która często składa się tylko z tych właściwości, które następnie były używane. Za pomocą projekcji można utworzyć nowy typ utworzony na podstawie każdego obiektu. Można projektować właściwość i wykonywać na niej funkcję matematyczną. Można również projektować oryginalny obiekt bez jego zmiany.

Standardowe metody operatorów zapytań, które wykonują projekcję, są wymienione w poniższej sekcji.

Metody

Nazwy metod opis Składnia wyrażeń zapytań języka C# Więcej informacji
Wybierz pozycję Wartości projektów oparte na funkcji transform. select Enumerable.Select
Queryable.Select
Selectmany Projekty sekwencje wartości opartych na funkcji przekształcania, a następnie spłaszczają je w jedną sekwencję. Używanie wielu from klauzul Enumerable.SelectMany
Queryable.SelectMany
Zip Tworzy sekwencję krotki z elementami z 2–3 określonych sekwencji. Nie dotyczy. Enumerable.Zip
Queryable.Zip

Select

W poniższym przykładzie użyto klauzuli select do projekcji pierwszej litery z każdego ciągu na liście ciągów.

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
*/

Równoważne zapytanie używające składni metody jest wyświetlane w następującym kodzie:

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

W poniższym przykładzie użyto wielu from klauzul do projekcji każdego wyrazu z każdego ciągu na liście ciągów.

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
*/

Równoważne zapytanie używające składni metody jest wyświetlane w następującym kodzie:

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
*/

Metoda SelectMany może również utworzyć kombinację dopasowania każdego elementu w pierwszej sekwencji z każdym elementem w drugiej sekwencji:

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

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

Równoważne zapytanie używające składni metody jest wyświetlane w następującym kodzie:

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

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

Zip

Operator projekcji Zip ma kilka przeciążeń. Zip Wszystkie metody działają na sekwencjach co najmniej dwóch typów heterogenicznych. Pierwsze dwa przeciążenia zwracają krotki z odpowiadającym im typem pozycyjnym z danej sekwencji.

Rozważ następujące kolekcje:

// 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'];

Aby projektować te sekwencje razem, użyj Enumerable.Zip<TFirst,TSecond>(IEnumerable<TFirst>, IEnumerable<TSecond>) operatora :

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'

Ważne

Wynikowa sekwencja z operacji zip nigdy nie jest dłuższa niż najkrótsza sekwencja. Kolekcje numbers i letters różnią się długością, a wynikowa sekwencja pomija ostatni element z numbers kolekcji, ponieważ nie ma nic do spakowania.

Drugie przeciążenie akceptuje third sekwencję. Utwórzmy kolejną kolekcję, czyli emoji:

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

Aby projektować te sekwencje razem, użyj Enumerable.Zip<TFirst,TSecond,TThird>(IEnumerable<TFirst>, IEnumerable<TSecond>, IEnumerable<TThird>) operatora :

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: 💜

Podobnie jak poprzednie przeciążenie, Zip metoda projektuje krotkę, ale tym razem z trzema elementami.

Trzecie przeciążenie akceptuje Func<TFirst, TSecond, TResult> argument, który działa jako selektor wyników. Możesz utworzyć nową sekwencję wynikową z spakowanej sekwencji.

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)

Po poprzednim Zip przeciążeniu określona funkcja jest stosowana do odpowiednich elementów numbers i letter, tworząc sekwencję string wyników.

Select a SelectMany

Praca obu Select elementów i SelectMany polega na utworzeniu wartości wyniku (lub wartości) z wartości źródłowych. Select tworzy jedną wartość wyniku dla każdej wartości źródłowej. Ogólny wynik jest zatem kolekcją, która ma taką samą liczbę elementów jak kolekcja źródłowa. SelectMany Natomiast tworzy pojedynczy ogólny wynik zawierający łączenie podkolekcje z każdej wartości źródłowej. Funkcja transform, która jest przekazywana jako argument SelectMany , musi zwrócić sekwencję wyliczalną wartości dla każdej wartości źródłowej. SelectMany Łączy te sekwencje wyliczalne w celu utworzenia jednej dużej sekwencji.

Na poniższych dwóch ilustracjach przedstawiono koncepcyjną różnicę między akcjami tych dwóch metod. W każdym przypadku załóżmy, że funkcja selektora (przekształcania) wybiera tablicę kwiatów z każdej wartości źródłowej.

Ta ilustracja przedstawia sposób Select zwracania kolekcji zawierającej taką samą liczbę elementów jak kolekcja źródłowa.

Grafika przedstawiająca akcję Select()

Ta ilustracja przedstawia sposób SelectMany łączenia pośredniej sekwencji tablic w jedną ostateczną wartość wyniku zawierającą każdą wartość z każdej tablicy pośredniej.

Grafika przedstawiająca akcję SelectMany()

Przykład kodu

Poniższy przykład porównuje zachowanie elementów Select i SelectMany. Kod tworzy "bukiet" kwiatów, biorąc elementy z każdej listy nazw kwiatów w kolekcji źródłowej. W poniższym przykładzie "pojedyncza wartość", która jest używana przez funkcję Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) transform, jest kolekcją wartości. Ten przykład wymaga dodatkowej foreach pętli w celu wyliczenia każdego ciągu w każdej podsekwencji.

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

Zobacz też