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.
Ważne
Te przykłady używają System.Collections.Generic.IEnumerable<T> źródła danych. Źródła danych oparte na System.Linq.IQueryProviderSystem.Linq.IQueryable<T> źródłach danych i drzewach wyrażeń. Drzewa wyrażeń mają ograniczenia dotyczące dozwolonej składni języka C#. Ponadto każde IQueryProvider
źródło danych, takie jak EF Core , może nakładać więcej ograniczeń. Zapoznaj się z dokumentacją źródła danych.
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.
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.
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);
}
}