分享方式:


集合運算式 - C# 語言參考

您可以使用集合運算式來建立一般集合值。 集合運算式是一種 terse 語法,在評估時,可以指派給許多不同的集合型別。 集合運算式包含 [] 方括弧之間的元素序列。 下列範例會宣告 string 元素的 System.Span<T>,並將其初始化為一週的日子:

Span<string> weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
foreach (var day in weekDays)
{
    Console.WriteLine(day);
}

集合運算式可以轉換成許多不同的集合型別。 第一個範例示範如何使用集合運算式初始化變數。 下列程式碼顯示您可以使用集合運算式的其他許多位置:

// Initialize private field:
private static readonly ImmutableArray<string> _months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

// property with expression body:
public IEnumerable<int> MaxDays =>
    [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

public int Sum(IEnumerable<int> values) =>
    values.Sum();

public void Example()
{
    // As a parameter:
    int sum = Sum([1, 2, 3, 4, 5]);
}

您無法在預期存在編譯時間常數時,使用集合運算式,例如初始化常數,或做為方法引述的預設值。

上述兩個範例都使用常數作為集合運算式的元素。 您也可以使用元素的變數,如下列範例所示:

string hydrogen = "H";
string helium = "He";
string lithium = "Li";
string beryllium = "Be";
string boron = "B";
string carbon = "C";
string nitrogen = "N";
string oxygen = "O";
string fluorine = "F";
string neon = "Ne";
string[] elements = [hydrogen, helium, lithium, beryllium, boron, carbon, nitrogen, oxygen, fluorine, neon];
foreach (var element in elements)
{
    Console.WriteLine(element);
}

Spread 元素

您可以使用「擴張元素」 .. 在集合運算式中內嵌集合值。 以下範例透過組合母音集合、子音集合和字母 "y" 來建立完整字母表的集合,而字母 "y" 可以是:

string[] vowels = ["a", "e", "i", "o", "u"];
string[] consonants = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
                       "n", "p", "q", "r", "s", "t", "v", "w", "x", "z"];
string[] alphabet = [.. vowels, .. consonants, "y"];

評估時,spread 元素 ..vowels 會產生五個元素:"a""e""i""o""u"。 spread 元素 ..consonants 會產生 20 個元素,也就是 consonants 陣列中的數字。 必須使用 foreach 陳述式來列舉 spread 元素中的變數。 如上一個範例所示,您可以將 spread 元素與集合運算式中的個別元素結合。

轉換

集合運算式可以轉換成不同的集合型別,包含:

重要

集合運算式一律會建立包含集合運算式中所有元素的集合,不論轉換的目標類型為何。 例如,當轉換的目標為 System.Collections.Generic.IEnumerable<T> 時,產生的程式碼會評估集合運算式,並將結果儲存在記憶體內部集合中。

此行為與 LINQ 不同,在列舉序列之前,可能不會具現化序列。 您無法使用集合運算式來產生不會列舉的無限序列。

編譯器會使用靜態分析以確定最佳效能方法,以使用集合運算式建立宣告的集合。 例如,如果初始化後不修改目標,則空集合運算式 [] 可以實現為 Array.Empty<T>()。 當目標為 System.Span<T>System.ReadOnlySpan<T> 時,儲存體可能會配置堆疊。 集合運算式功能規格會指定編譯器必須遵循的規則。

許多 API 會以多個集合型別作為參數來多載。 因為集合運算式可以轉換成許多不同的運算式類型,因此這些 API 可能需要在集合運算式上轉換,以指定正確的轉換。 下列轉換規則可解決一些模棱兩可的情況:

當集合運算式轉換成 SpanReadOnlySpan 時,span 物件的安全內容會取自範圍中包含的所有元素的安全內容。 如需詳細規則,請參閱集合運算式規格 (部分機器翻譯)。

集合建立器

集合運算式會使用任何「表現良好」的集合類型。 表現良好的關聯具有下列特性:

  • 可計算集合上 CountLength 的值會產生與列舉時的元素數目相同的值。
  • System.Collections.Generic 命名空間中的類型假設為無副作用。 因此,編譯器可以將這類類型可能用作中繼值 (否則不會公開) 的案例最佳化。
  • 呼叫集合上某些適用的 .AddRange(x) 成員,將會產生與逐一查看 x 相同的最終值,並將其所有列舉值個別新增至具有 .Add 的集合。

.NET 執行階段中的所有集合類型都表現良好。

警告

如果自訂集合類型的表現不佳,則不會定義當您使用該集合類型搭配集合運算式時的行為。

透過編寫 Create() 方法並對集合類型套用 System.Runtime.CompilerServices.CollectionBuilderAttribute 以指示建立器方法,您的類型可以選擇加入集合運算式支援。 例如,請考慮使用 80 個字元的固定長度緩衝區的應用程式。 該類別看起來類似下列程式碼:

public class LineBuffer : IEnumerable<char>
{
    private readonly char[] _buffer = new char[80];

    public LineBuffer(ReadOnlySpan<char> buffer)
    {
        int number = (_buffer.Length < buffer.Length) ? _buffer.Length : buffer.Length;
        for (int i = 0; i < number; i++)
        {
            _buffer[i] = buffer[i];
        }
    }

    public IEnumerator<char> GetEnumerator() => _buffer.AsEnumerable<char>().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => _buffer.GetEnumerator();

    // etc
}

您希望將其與集合運算式搭配使用,如以下範例所示:

LineBuffer line = ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'];

LineBuffer 型別會實作 IEnumerable<char>,因此編譯器會將它辨識為 char 項目的集合。 實作的 System.Collections.Generic.IEnumerable<T> 介面的型別參數表示元素型別。 您必須將兩個新增項目新增至應用程式,才能將集合運算式指派給 LineBuffer 物件。 首先,您必須建立包含 Create 方法的類別:

internal static class LineBufferBuilder
{
    internal static LineBuffer Create(ReadOnlySpan<char> values) => new LineBuffer(values);
}

Create 方法必須傳回 LineBuffer 物件,而且必須採用型別 ReadOnlySpan<char> 的單一參數。 ReadOnlySpan 的型別參數必須符合集合的元素類型。 傳回泛型集合的建立器方法會將泛型 ReadOnlySpan<T> 當做其參數。 方法必須可存取與static

最後,您必須將 CollectionBuilderAttribute 新增至 LineBuffer 類別宣告:

[CollectionBuilder(typeof(LineBufferBuilder), "Create")]

第一個參數會提供 Builder 類別的名稱。 第二個屬性會提供建立器方法的名稱。