Partilhar via


Operações de projeção (C#)

Projeção refere-se à operação de transformar um objeto em uma nova forma que muitas vezes consiste apenas nas propriedades posteriormente usadas. Usando projeção, você pode construir um novo tipo que é construído a partir de cada objeto. Você pode projetar uma propriedade e executar uma função matemática nela. Você também pode projetar o objeto original sem alterá-lo.

Importante

Esses exemplos usam uma fonte de System.Collections.Generic.IEnumerable<T> dados. Fontes de dados baseadas em System.Linq.IQueryProvider utilizam fontes de dados System.Linq.IQueryable<T> e árvores de expressão . As árvores de expressão têm limitações na sintaxe C# permitida. Além disso, cada IQueryProvider fonte de dados, como o EF Core , pode impor mais restrições. Verifique a documentação da sua fonte de dados.

Os métodos de operador de consulta padrão que executam a projeção estão listados na seção a seguir.

Metodologia

Nomes de método Descrição Sintaxe da expressão de consulta C# Mais informações
Selecionar Projeta valores baseados em uma função de transformação. select Enumerable.Select
Queryable.Select
Selecionar Vários Projeta sequências de valores que se baseiam em uma função de transformação e, em seguida, as nivela em uma sequência. Use várias from cláusulas Enumerable.SelectMany
Queryable.SelectMany
Código Postal Produz uma sequência de tuplas com elementos a partir de 2 ou 3 sequências especificadas. Não aplicável. Enumerable.Zip
Queryable.Zip

Select

O exemplo a seguir usa a select cláusula para projetar a primeira letra de cada cadeia de caracteres em uma lista de cadeias de caracteres.

Observação

Você pode consultar as fontes de dados comuns para essa área no artigo Visão geral dos operadores de consulta padrão .

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

A consulta equivalente usando sintaxe de método é mostrada no código a seguir:

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

O exemplo a seguir usa várias from cláusulas para projetar cada palavra de cada cadeia de caracteres em uma lista de cadeias de caracteres.

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

A consulta equivalente usando sintaxe de método é mostrada no código a seguir:

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

O SelectMany método também pode formar a combinação que associa cada item na primeira sequência com cada item na segunda sequência.

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

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

A consulta equivalente usando sintaxe de método é mostrada no código a seguir:

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

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

Zip

Existem vários sobrecarregamentos para o operador de projeção Zip. Todos os Zip métodos funcionam em sequências de dois ou mais tipos possivelmente heterogéneos. As duas primeiras sobrecargas retornam tuplas, com o tipo posicional correspondente das sequências dadas.

Considere as seguintes coleções:

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

Para projetar essas sequências juntas, use o Enumerable.Zip<TFirst,TSecond>(IEnumerable<TFirst>, IEnumerable<TSecond>) operador:

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'

Importante

A sequência resultante de uma operação zip nunca é maior do que a sequência mais curta. As coleções numbers e letters diferem em comprimento, e a sequência resultante omite o último elemento da coleção numbers, pois não há nada com que emparelhar.

A segunda sobrecarga aceita uma sequência third. Vamos criar outra coleção, a saber emoji:

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

Para projetar essas sequências juntas, use o Enumerable.Zip<TFirst,TSecond,TThird>(IEnumerable<TFirst>, IEnumerable<TSecond>, IEnumerable<TThird>) operador:

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

Assim como a sobrecarga anterior, o método Zip projeta uma tupla, mas desta vez com três elementos.

A terceira sobrecarga aceita um Func<TFirst, TSecond, TResult> argumento que atua como um seletor de resultados. Você pode projetar uma nova sequência resultante das sequências que estão sendo intercaladas.

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)

Com a sobrecarga anterior Zip, a função especificada é aplicada aos elementos correspondentes number e letter, produzindo uma sequência de resultados string.

Select versus SelectMany

O trabalho de ambos Select e SelectMany é produzir um valor de resultado (ou valores) a partir de valores de origem. Select Produz um valor de resultado para cada valor de origem. O resultado geral é, portanto, uma coleção que tem o mesmo número de elementos que a coleção de origem. Em contraste, SelectMany produz um único resultado geral que contém subcoleções concatenadas de cada valor de origem. A função transform que é passada como um argumento para SelectMany deve retornar uma sequência enumerável de valores para cada valor de origem. SelectMany Concatena essas sequências enumeráveis para criar uma sequência grande.

As duas ilustrações a seguir mostram a diferença conceitual entre as ações desses dois métodos. Em cada caso, assuma que a função de seleção (transformação) seleciona a matriz de flores de cada valor de origem.

Esta ilustração mostra como Select retorna uma coleção que tem o mesmo número de elementos que a coleção de origem.

Gráfico que mostra a ação de Select()

Esta ilustração mostra como SelectMany concatena a sequência intermediária de matrizes em um valor de resultado final que contém cada valor de cada matriz intermediária.

Gráfico mostrando a ação de SelectMany()

Exemplo de código

O exemplo a seguir compara o comportamento de Select e SelectMany. O código cria um "buquê" de flores pegando os itens de cada lista de nomes de flores na coleção de origem. No exemplo a seguir, o "valor único" que a função Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) de transformação usa é uma coleção de valores. Este exemplo requer o loop extra foreach para enumerar cada cadeia de caracteres em cada subsequência.

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

Ver também