共用方式為


投影作業 (C#)

投影是指通過操作,將對象轉換成新形態,這些形態通常只包含後續使用的屬性。 藉由使用投影,您可以建構從每個物件建置的新類型。 您可以投影屬性,並在其上執行數學函式。 您也可以投影原始物件,而不需變更它。

這很重要

這些範例會使用 System.Collections.Generic.IEnumerable<T> 資料來源。 根據 System.Linq.IQueryProvider 的資料來源會使用 System.Linq.IQueryable<T> 資料來源和運算式樹。 運算式樹在允許的 C# 語法方面有限制。 此外,每個 IQueryProvider 資料來源 (例如 EF Core) 可能會施加更多限制。 檢查資料來源的文件。

執行投影的標準查詢運算符方法會列在下一節中。

方法

方法名稱 說明 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(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
*/

方法 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

有數個多載版本的投影運算子 ZipZip所有方法都會處理兩個或多個可能異質類型的序列。 前兩個多載會傳回元組,包含來自指定序列的對應位置類型。

請考慮下列集合:

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

這很重要

ZIP 作業所產生的序列長度絕不會比最短序列長。 numbersletters 集合的長度不同,因此生成的序列會省略 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 多載時,指定的函式會套用至對應的元素 numberletter,以產生結果序列 string

SelectSelectMany

SelectSelectMany 的工作是從來源值產生一個或多個結果值。 Select 會為每個來源值產生一個結果值。 因此,整體結果是集合,其項目數目與來源集合相同。 相反地, SelectMany 會產生單一整體結果,其中包含來自每個來源值的串連子集合。 傳遞為 自變數的 SelectMany 轉換函式必須針對每個來源值傳回可列舉的值序列。 SelectMany 串連這些可列舉的序列,以建立一個大型序列。

下列兩個圖例顯示這兩種方法動作之間的概念差異。 在每個案例中,假設選取器 (transform) 函式會從每個來源值選取花朵陣列。

此圖描述如何 Select 傳回與來源集合具有相同項目數目的集合。

顯示 Select() 動作的圖形

此圖說明如何將 SelectMany 陣列的中繼序列串連成一個最終結果值,其中包含每個中繼數位中的每個值。

顯示 SelectMany() 動作的圖形

程式碼範例

下列範例會比較 SelectSelectMany 的行為。 程式碼透過從來源集合中的每個花名清單提取元素,以建立花朵的「花束」。 在下列範例中,轉換函 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);
    }
}

另請參閱