Операции проекции (C#)
Проекция относится к операции преобразования объекта в новую форму, которая часто состоит только из этих свойств, которые впоследствии используются. С помощью проекции можно создать новый тип, построенный из каждого объекта. Вы можете проецировать свойство и выполнять над ним математические функции. Также можно проецировать исходный объект, не изменяя его.
Внимание
В этих примерах используется System.Collections.Generic.IEnumerable<T> источник данных. Источники данных, основанные на System.Linq.IQueryProvider использовании System.Linq.IQueryable<T> источников данных и деревьев выражений. Деревья выражений имеют ограничения на допустимый синтаксис C#. Кроме того, каждый IQueryProvider
источник данных, например EF Core , может наложить больше ограничений. Ознакомьтесь с документацией по источнику данных.
Методы стандартных операторов запросов, которые выполняют проецирование, перечислены в следующем разделе.
Методы
Имена методов | Description | Синтаксис выражения запроса C# | Дополнительные сведения |
---|---|---|---|
Выбрать | Проецирует значения, основанные на функции преобразования. | select |
Enumerable.Select Queryable.Select |
SelectMany | Проецирует последовательности значений, основанных на функции преобразования, а затем выравнивает их в одну последовательность. | Использование нескольких предложений from |
Enumerable.SelectMany Queryable.SelectMany |
Почтовый индекс | Создает последовательность кортежей с элементами из 2–3 указанных последовательностей. | Неприменимо. | Enumerable.Zip Queryable.Zip |
Select
В приведенном ниже примере предложение select
используется для проецирования первой буквы из каждой строки в списке строк.
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
*/
Эквивалентный запрос с использованием синтаксиса метода показан в следующем коде:
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
В приведенном ниже примере несколько предложений from
используются для проецирования каждого слова из каждой строки в списке строк.
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
*/
Эквивалентный запрос с использованием синтаксиса метода показан в следующем коде:
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
*/
Метод SelectMany
также может сформировать комбинацию сопоставления каждого элемента в первой последовательности с каждым элементом во второй последовательности:
var query = from number in numbers
from letter in letters
select (number, letter);
foreach (var item in query)
{
Console.WriteLine(item);
}
Эквивалентный запрос с использованием синтаксиса метода показан в следующем коде:
var method = numbers
.SelectMany(number => letters,
(number, letter) => (number, letter));
foreach (var item in method)
{
Console.WriteLine(item);
}
Zip
Существует несколько перегрузок для оператора проекции Zip
. Все методы Zip
работают для последовательностей с двумя и более потенциально разнородными типами. Первые две перегрузки возвращают кортежи и соответствующий позиционный тип из заданных последовательностей.
Давайте рассмотрим следующие коллекции:
// 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'];
Чтобы объединить эти последовательности с помощью проекции, примените оператор 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'
Внимание
Последовательность, полученная в результате операции объединения, никогда не будет длиннее кратчайшей последовательности. Коллекции numbers
и letters
отличаются по длине, а результирующая последовательность опускает последний элемент из коллекции numbers
, так как ее не с чем объединять.
Вторая перегрузка принимает последовательность third
. Давайте создадим еще одну коллекцию, а именно emoji
:
// A string array with 8 elements.
IEnumerable<string> emoji = [ "🤓", "🔥", "🎉", "👀", "⭐", "💜", "✔", "💯"];
Чтобы объединить эти последовательности с помощью проекции, примените оператор 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: 💜
Как и предыдущая перегрузка, метод Zip
проецирует кортеж, но на этот раз с тремя элементами.
Третья перегрузка принимает аргумент Func<TFirst, TSecond, TResult>
, который выступает в качестве селектора результатов. Вы можете проецирует новую результирующая последовательность из архивируемых последовательностей.
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)
В предыдущей перегрузке Zip
указанная функция применяется к соответствующим элементам numbers
и letter
, возвращая последовательность результатов string
.
Select
и SelectMany
Задача обоих методов Select
и SelectMany
заключается в создании результирующего значения (или значений) из исходных значений. Select
создает один результат для каждого исходного значения. Таким образом, общий результат является коллекцией, имеющей то же количество элементов, что и исходная коллекция. В отличие от этого, SelectMany
создается один общий результат, содержащий объединенные подколлекции из каждого исходного значения. Функция преобразования, которая передается в качестве аргумента методу SelectMany
, должна возвращать перечисляемую последовательность значений для каждого исходного значения. SelectMany
объединяет эти перечисленные последовательности для создания одной большой последовательности.
На двух рисунках, приведенных ниже, показаны принципиальные различия между работой этих двух методов. В обоих случаях предполагается, что функция выбора (преобразования) выбирает массив цветов из каждого исходного значения.
На этом рисунке представлено, как Select
возвращает коллекцию, которая имеет то же количество элементов, что и исходная коллекция.
На этом рисунке показано, как SelectMany
объединяет промежуточные последовательности массивов в один конечный результат, содержащий все значения из промежуточных массивов.
Пример кода
В приведенном ниже примере сравнивается действие Select
и SelectMany
. Код создает "букет" цветов, принимая элементы из каждого списка имен цветов в исходной коллекции. В следующем примере "одно значение", которое использует функция Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) преобразования, является коллекцией значений. В этом примере требуется дополнительный foreach
цикл для перечисления каждой строки в каждой подсеку.
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);
}
}