首页 / C# 入门教程 / 集合表达式

C# 入门教程

集合表达式

本教程共 100 篇 · 第 26 篇 · 更新于 2026-07-31 · 约 8 分钟阅读

C#C# 入门教程编程语言集合表达式C# 12

26. 集合表达式

本节目标:用 C# 12 的集合表达式 [...] 一行创建数组、List、Span,并理解空集合与展开元素的用法。

为什么要发明集合表达式

在没有集合表达式(collection expression)之前,创建集合的写法很分裂:数组要用 new int[] { 1, 2, 3 }List<T> 要用 new List<int> { 1, 2, 3 }Span<T> 几乎没有方便的写法。同一种”给我一组数”的意图,却要记三种不同语法,重构时想在数组和列表之间切换也麻烦。

C# 12 引入集合表达式,用一对方括号 [...] 统一表达”一组元素”,具体变成哪种集合,由编译器根据上下文(目标类型)决定。一处写法,多种类型通吃,这就是它最大的价值。

Note

集合表达式不是一种新的集合类型,而是一个”目标类型(target-typed)“的语法糖。编译器看你要把它赋给 int[] 还是 List<int>,就生成对应的底层代码。你写的是同一行,编译器负责适配。

基础写法

方括号里直接列元素,右边变量的类型决定最终产物:

namespace CSharpDemo;

int[] numbers = [1, 2, 3];
List<string> names = ["小明", "小红"];
Span<int> digits = [0, 1, 2];

Console.WriteLine(numbers.Length);   // 3
Console.WriteLine(names[0]);         // 小明
Console.WriteLine(digits[2]);        // 2

把代码读一遍:numbers 是数组,names 是列表,digitsSpan。三行写法几乎一致,差别只在左边声明的类型。

能用在哪些类型上

从 C# 12 起,集合表达式支持的类型包括:

  • 数组(array):int[]string[]Person[]
  • Span<T>ReadOnlySpan<T>
  • 实现了集合初始化器的类型,最常见的是 List<T>
  • 还有 IEnumerable<T>ImmutableArray<T>HashSet<T>Stack<T>Queue<T> 等满足构造约定的类型
namespace CSharpDemo;

Span<char> chars = ['a', 'b', 'c'];
List<int> nums = [10, 20, 30];
HashSet<int> unique = [1, 2, 2, 3];   // 自动去重成 1,2,3

Console.WriteLine(unique.Count);  // 3
Tip

把集合表达式赋给接口类型(如 IEnumerable<int> many = [1, 2, 3];)时,编译器会挑一个合适的实现(通常是数组)来存放。需要具体行为时,显式声明成 List<int> 更清晰、更可控。

空集合 []

一对空方括号表示”空的集合”,同样能赋给各种类型。它比 new List<string>() 更短,也比数组的 Array.Empty<string>() 更直观。

namespace CSharpDemo;

List<string> emptyList = [];
int[] emptyArray = [];

Console.WriteLine(emptyList.Count);  // 0
Console.WriteLine(emptyArray.Length); // 0
Note

[] 不是 null,它是一个真实存在、元素个数为 0 的空集合。所以你拿它去遍历、取 Count 都不会空引用异常。这点和返回 null 的旧习惯不同,更安全,调用方往往可以省掉判空。

展开元素 ..

集合表达式里可以用 ..(两个点)把另一个集合”铺平”塞进来,官方叫 spread element(展开元素)。它像把一段集合拆开、逐个放入结果,而不是把”整个集合”当成一个元素塞进去。

namespace CSharpDemo;

int[] part1 = [1, 2, 3];
int[] part2 = [4, 5];
int[] combined = [.. part1, .. part2];

Console.WriteLine(string.Join(" ", combined));  // 1 2 3 4 5

.. part1part1 里的每个元素依次放进 combined,不是把”整个数组”当成一个元素。

Warning

.. 后面必须跟一个可枚举的集合(数组、List<T> 等)。写成 .. 5 这种”展开一个单独的数”会编译报错——展开的是集合,不是单个标量值。

混合与嵌套

展开元素可以和普通元素混写,也能拼出交错数组(数组的数组):

namespace CSharpDemo;

int[] part1 = [1, 2, 3];
int[] part2 = [4, 5];
int[] baseNums = [2, 3];
int[] mixed = [1, .. baseNums, 4];      // 1 2 3 4

int[][] grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
Console.WriteLine(grid[1][2]);          // 6

int[] extended = [.. mixed, .. part2];  // 1 2 3 4 4 5
Console.WriteLine(extended.Length);     // 6

要注意:.. part1 是复制元素,修改 combined 不会影响原来的 part1,两者是独立的。上面在开头声明了 part1/part2,方便示例自包含可运行。

Tip

想把几个小数组合并成大数组,以前要写循环或 Concat。现在一行 [.. a, .. b, .. c] 就搞定,可读性高很多,也更容易一眼看清最终内容。

直接当参数和返回值

集合表达式不只是赋值好用,还能直接作为方法参数或返回值,省掉临时变量:

namespace CSharpDemo;

int Sum(IEnumerable<int> values)
{
    int total = 0;
    foreach (var v in values) total += v;
    return total;
}

Console.WriteLine(Sum([1, 2, 3, 4]));     // 10

int[] make() => [9, 8, 7];
Console.WriteLine(make()[0]);             // 9

和集合初始化器怎么选

老代码的 new List<int> { 1, 2, 3 }(集合初始化器,collection initializer)依然完全有效,两种写法可以共存。但集合表达式明显更统一:数组、List、Span 用同一套方括号语法,将来在数组和 List 之间切换,只需改左边类型,不用动右边内容。

// 老:new List<int> { 1, 2, 3 }
// 新:List<int> nums = [1, 2, 3];
Note

集合表达式从 C# 12 开始可用。本教程基线为 C# 14 / .NET 10,大可放心使用,不必担心语言版本过低报错。它属于”语法糖”,运行效率与对应老写法基本一致,主要收益在简洁和一致。

常见坑

  1. 展开的不是单个值.. 后面必须是集合,别写成展开标量。
  2. 它是副本不是视图[.. a] 复制了 a 的元素,之后改原数组不影响新集合,反之亦然。
  3. 目标类型要明确:写 var x = [1, 2, 3]; 时,编译器会把 x 推断成 int[]。想要 List<int> 就显式声明类型。
  4. 别误以为能改:集合表达式造出的是普通集合,之后该 AddAdd、该赋值索引就赋值,它只是”创建”阶段的简写。

最佳实践

  • 创建集合优先用 [...],少写 new List<T> { }
  • 合并小集合用 [.. a, .. b],比手写了循环清爽。
  • 需要空集合就写 [],比返回 null 更安全,调用方不用判空。
  • 当参数或返回值时,按真实需要声明成数组还是 List<T>,别图省事一律用 IEnumerable<T> 而造成不必要的分配。

集合表达式与可读性

写上 [] 之后,代码的”意图”比”语法”更突出。读者一眼看到的是”这是一组元素”,而不是”在调构造函数、加初始化器”。在团队代码评审里,这种少噪音的写法更容易扫读,新人也能更快看懂。

另外,集合表达式让”数组还是列表”变成了一个可以在声明处轻松切换的细节。过去把某字段从 int[] 改成 List<int>,每个 new int[]{...} 都得跟着改;现在只需改左侧类型,初始化处的 [1,2,3] 一字不动。

Tip

当一份数据在多处被当作集合传递时,统一用集合表达式初始化,将来在数组和 List<T> 之间迁移几乎零成本。

一个容易混淆的点

有人会问:集合表达式是”数组语法”吗?不是。它只是长得很像数组字面量,最终类型完全由左边决定。把它理解成”一个会被编译器翻译成正确集合的速记法”最准确。

Note

因为类型由目标决定,把集合表达式赋给 var 时,编译器会选择数组作为默认实现。所以显式写出左侧类型,往往比 var 更清楚表达意图。开发中不必纠结,记住”左侧类型说了算”即可。

小结

集合表达式用 [...] 一种语法通吃数组、List、Span,配 .. 展开元素能合并、嵌套,空集合用 []。它是 C# 12 起最实用的语法糖之一,让”造一组东西”这件事变得又短又一致。下一章我们换个角度,看字符串本身如何被当成一组字符来操作。