Operace projekce (C#)

Projekce odkazuje na operaci transformace objektu na nový formulář, který se často skládá pouze z těchto vlastností následně použitých. Pomocí projekce můžete vytvořit nový typ vytvořený z každého objektu. Vlastnost můžete projektovat a provádět s ní matematickou funkci. Můžete také projektovat původní objekt beze změny.

Důležité

Tyto ukázky používají System.Collections.Generic.IEnumerable<T> zdroj dat. Zdroje dat založené na System.Linq.IQueryProvider použití System.Linq.IQueryable<T> zdrojů dat a stromů výrazů Stromy výrazů mají omezení povolené syntaxe jazyka C#. Každý zdroj dat, například EF Core, IQueryProvider může navíc uplatňovat další omezení. Projděte si dokumentaci ke zdroji dat.

Standardní metody operátoru dotazu, které provádějí projekci, jsou uvedeny v následující části.

Metody

Názvy metod Popis Syntaxe výrazu dotazu jazyka C# Více informací
Vyberte Hodnoty projektů založené na transformační funkci select Enumerable.Select
Queryable.Select
Selectmany Projekty sekvence hodnot, které jsou založeny na transformační funkci a pak je zploštějí do jedné sekvence. Použití více from klauzulí Enumerable.SelectMany
Queryable.SelectMany
PSČ Vytvoří sekvenci řazených kolekcí členů s prvky ze 2 až 3 zadaných sekvencí. Nevztahuje se. Enumerable.Zip
Queryable.Zip

Select

Následující příklad pomocí select klauzule projektuje první písmeno z každého řetězce v seznamu řetězců.

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

Ekvivalentní dotaz pomocí syntaxe metody se zobrazí v následujícím kódu:

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

Následující příklad používá více from klauzulí k projektování každého slova z každého řetězce v seznamu řetězců.

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

Ekvivalentní dotaz pomocí syntaxe metody se zobrazí v následujícím kódu:

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 může také vytvořit kombinaci shody každé položky v první sekvenci s každou položkou ve druhé sekvenci:

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

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

Ekvivalentní dotaz pomocí syntaxe metody se zobrazí v následujícím kódu:

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

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

Zip

Operátor projekce má několik přetížení Zip . Zip Všechny metody pracují na sekvencích dvou nebo více možná heterogenních typů. První dvě přetížení vrací řazené kolekce členů s odpovídajícím pozičním typem z daných sekvencí.

Zvažte následující kolekce:

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

Pokud chcete tyto sekvence promítnout dohromady, použijte Enumerable.Zip<TFirst,TSecond>(IEnumerable<TFirst>, IEnumerable<TSecond>) operátor:

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'

Důležité

Výsledná posloupnost operace zipu je nikdy delší než nejkratší sekvence. letters Kolekce numbers se liší délkou a výsledná sekvence vynechá poslední prvek z numbers kolekce, protože nemá co zazipovat.

Druhé přetížení přijímá third sekvenci. Pojďme vytvořit další kolekci, konkrétně emoji:

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

Pokud chcete tyto sekvence promítnout dohromady, použijte Enumerable.Zip<TFirst,TSecond,TThird>(IEnumerable<TFirst>, IEnumerable<TSecond>, IEnumerable<TThird>) operátor:

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

Podobně jako předchozí přetížení Zip metoda projektuje řazenou kolekci členů, ale tentokrát se třemi prvky.

Třetí přetížení přijímá Func<TFirst, TSecond, TResult> argument, který funguje jako selektor výsledků. Můžete promítt novou výslednou sekvenci ze zazipovaných sekvencí.

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)

Při předchozím Zip přetížení se zadaná funkce použije na odpovídající prvky numbers a lettervytvoří sekvenci string výsledků.

Select versus SelectMany

Práce obou Select a SelectMany je vytvořit výslednou hodnotu (nebo hodnoty) ze zdrojových hodnot. Select vytvoří jednu výslednou hodnotu pro každou zdrojnou hodnotu. Celkový výsledek je tedy kolekce, která má stejný počet prvků jako zdrojová kolekce. Naproti tomu vytvoří jeden celkový výsledek, SelectMany který obsahuje zřetězené dílčí kolekce z každé zdrojové hodnoty. Transformační funkce, která je předána jako argument, SelectMany musí vrátit výčet hodnot pro každou zdrojovou hodnotu. SelectMany zřetězí tyto výčtové sekvence a vytvoří jednu velkou sekvenci.

Následující dva ilustrace ukazují koncepční rozdíl mezi akcemi těchto dvou metod. V každém případě předpokládejme, že funkce selektoru (transformace) vybere pole květin z každé zdrojové hodnoty.

Tento obrázek znázorňuje, jak Select vrátí kolekci, která má stejný počet prvků jako zdrojová kolekce.

Obrázek znázorňující akci select()

Tento obrázek znázorňuje, jak SelectMany zřetězí průběžnou sekvenci polí do jedné konečné výsledné hodnoty, která obsahuje každou hodnotu z každého zprostředkujícího pole.

Obrázek znázorňující akci SelectMany()

Příklad kódu

Následující příklad porovnává chování Select a SelectMany. Kód vytvoří "kytice" květin tím, že vezme položky z každého seznamu názvů květin ve zdrojové kolekci. V následujícím příkladu je "jednoduchá hodnota", kterou transformační funkce Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) používá, kolekcí hodnot. Tento příklad vyžaduje dodatečnou foreach smyčku, aby se každý řetězec v jednotlivých dílčích sekvencích vyčíslil.

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

Viz také