集合運算式 - 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.Span<T> 和 System.ReadOnlySpan<T>。
- 陣列。
- 具有參數型別為
ReadOnlySpan<T>
的 create 方法的任何型別,其中存在從集合運算式型別到T
的隱含轉換。 - 支援集合初始設定式的任何型別,例如 System.Collections.Generic.List<T>。 此需求通常表示型別支援 System.Collections.Generic.IEnumerable<T>,而且有一個可存取的
Add
方法可將項目新增至集合。 必須有一個從集合運算式元素型別到集合元素型別的隱含轉換。 對於 spread 元素,必須有一個從 spread 元素型別轉換成集合元素型別的隱含轉換。 - 下列任何介面:
重要
集合運算式一律會建立包含集合運算式中所有元素的集合,不論轉換的目標類型為何。 例如,當轉換的目標為 System.Collections.Generic.IEnumerable<T> 時,產生的程式碼會評估集合運算式,並將結果儲存在記憶體內部集合中。
此行為與 LINQ 不同,在列舉序列之前,可能不會具現化序列。 您無法使用集合運算式來產生不會列舉的無限序列。
編譯器會使用靜態分析以確定最佳效能方法,以使用集合運算式建立宣告的集合。 例如,如果初始化後不修改目標,則空集合運算式 []
可以實現為 Array.Empty<T>()。 當目標為 System.Span<T> 或 System.ReadOnlySpan<T> 時,儲存體可能會配置堆疊。 集合運算式功能規格會指定編譯器必須遵循的規則。
許多 API 會以多個集合型別作為參數來多載。 因為集合運算式可以轉換成許多不同的運算式類型,因此這些 API 可能需要在集合運算式上轉換,以指定正確的轉換。 下列轉換規則可解決一些模棱兩可的情況:
- 轉換為 Span<T>、ReadOnlySpan<T> 或其他
ref struct
型別優於轉換為非 ref 結構型別。 - 轉換為非介面型別優於轉換為介面型別。
當集合運算式轉換成 Span
或 ReadOnlySpan
時,span 物件的安全內容會取自範圍中包含的所有元素的安全內容。 如需詳細規則,請參閱集合運算式規格 (部分機器翻譯)。
集合建立器
集合運算式會使用任何「表現良好」的集合類型。 表現良好的關聯具有下列特性:
- 可計算集合上
Count
或Length
的值會產生與列舉時的元素數目相同的值。 - 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 類別的名稱。 第二個屬性會提供建立器方法的名稱。