C# 12 集合表达式详解:用 `[...]` 统一数组、List 与 Span 初始化

Nora
Nora
发布时间:2026-08-25 | 151人浏览过
原创

前言

C# 在集合初始化上长期存在写法分散的问题:数组、`List<T>`、`Span<T>` 甚至自定义集合,语法各不一样。到了 `C# 12.0` 和 `.NET 8.0`,集合表达式用统一的 `[...]` 语法把这些场景串了起来。本文会从它解决的问题、核心语法、支持范围和使用边界几个角度重写梳理,帮助你判断它在项目里该怎么用、值不值得推广。

C# 在集合初始化这件事上,过去一直存在写法分散的问题:数组、List<T>、Span<T> 甚至自定义集合,语法各不相同。到了 C# 12.0 与 .NET 8.0,集合表达式用统一的 [...] 语法把这些场景收拢起来。本文会从它解决的问题、核心语法、支持范围和适用边界几个角度展开,帮助你快速看懂这项新特性值不值得在项目里采用。

集合表达式解决了什么问题

集合表达式(Collection Expressions)是 2023 年随 .NET 8.0 发布、由 C# 12.0 引入的新特性。它的目标很直接:用更短、更统一、更接近声明式的方式创建和初始化集合。

在旧写法里,不同集合往往要记不同模板:

  • 数组常写成 new[] { ... } 或 new int[] { ... }
  • List<T> 常写成 new List<T> { ... }
  • 部分场景还要考虑目标类型推断、空集合写法和嵌套结构

集合表达式统一改成 [...] 之后,代码会更集中,阅读时也更容易一眼看出“这是一个集合值”,而不是被不同的构造形式打断。

这项能力尤其适合下面几类场景:

  • 数据初始化:快速定义数组、列表、字典等集合
  • 批量操作:例如配合 MySqlBulkLoader 处理批量数据填充
  • API 输入/输出:组织 JSON 响应或请求中的集合数据
  • 缓存管理:初始化 MemoryCache 的键值集合
  • 定时任务:结合 Cronos 组织批处理数据

核心语法怎么写

集合表达式的基本形式只有一个核心:[element1, element2, ..., elementN]。它看起来很轻,但背后依然会根据目标类型转换成对应集合。

集合表达式与传统集合初始化写法对比信息图
传统写法与 `[...]` 的差别用对比方式展示 C# 12 集合表达式如何统一数组、List 和 Span 的初始化入口。

传统初始化与新写法对比

// 传统方式  
List<int> list1 = new List<int> { 1, 2, 3 };  
int[] array1 = new int[] { 1, 2, 3 };  

// C# 12 集合表达式  
List<int> list2 = [1, 2, 3];  
int[] array2 = [1, 2, 3];  
Span<int> span = [1, 2, 3];  

从可读性看,最明显的变化是去掉了重复出现的 new、类型名和花括号模板。尤其在方法参数、返回值或者嵌套结构里,简化效果会更明显。

几种最常用的写法

基础初始化:

int[] numbers = [1, 2, 3]; // 等价于 new[] { 1, 2, 3 }
List<string> names = ["Alice", "Bob"]; // 等价于 new List<string> { "Alice", "Bob" }

空集合:

int[] empty = []; // 等价于 Array.Empty<int>()

嵌套集合:

int[][] jagged = [[1, 2], [3, 4]]; // 锯齿状数组

另一种常见的嵌套形式如下:

// 嵌套集合  
int[][] matrix = [[1, 2], [3, 4], [5, 6]];  

扩展运算符与类型推断

集合表达式不只是“少写几个字”,它还支持通过 .. 展开已有集合,这在合并数组或列表时很实用。

int[] moreNumbers = [1, 2, ..numbers, 4]; // 展开 numbers,生成 [1, 2, 1, 2, 3, 4]

它也支持目标类型推断,也就是编译器根据上下文决定最终集合类型:

IEnumerable<int> enumerable = [1, 2, 3]; // 推断为数组

这意味着你在接口类型、方法参数和返回值位置使用集合表达式时,通常可以少写一层显式构造代码。不过也正因为依赖目标类型,阅读时最好确认上下文,避免把“看起来像列表”的表达式误解成固定实现类型。

它支持哪些集合类型

这项特性不是只给数组和 List<T> 用的。根据给定上下文,编译器可以把集合表达式转换到多种目标类型。

C# 集合表达式支持类型与编译器转换路径信息图
支持范围与转换方式把集合表达式可落到的目标类型和对应转换方式放在一张图里,便于快速判断适用范围。
集合类型示例编译器转换机制
数组int[] arr = [1, 2, 3];直接优化为 new int[] { ... }
ListList list = [1, 2, 3];调用 List.CollectionsMarshalHelper.Create()
SpanSpan span = [1, 2, 3];栈分配或缓冲区重用
ReadOnlySpanReadOnlySpan ros = ['a','b'];同 Span
ImmutableArrayImmutableArray ia = [1,2,3];调用 ImmutableArray.Create()
自定义集合实现 IEnumerable + Add 方法编译器生成 Add 调用序列

✅ 所有实现 IEnumerable 且有 Add 方法的类型都支持

从使用角度看,可以把支持范围分成三类来理解:

内建目标类型

  • 数组
  • List<T>
  • Span<T>
  • ReadOnlySpan<T>
  • ImmutableArray

这些类型一般都有较明确的编译器处理路径,因此既保留了简洁语法,也兼顾了性能优化空间。

接口类型与兼容性

集合表达式也能对接常见抽象接口,例如:

  • IEnumerable<T>
  • ICollection<T>

这让它非常适合写在方法返回值、配置对象初始化和中间结果拼装上。代码层面仍然是一个集合值,但调用端不需要关注底层具体用的是哪种实现。

自定义集合怎么接入

如果你有自己的集合类型,只要满足两个基本条件,也可以直接吃到这套语法:

  • 实现 IEnumerable
  • 提供 Add 方法

例如:

public class CustomCollection<T> : IEnumerable<T>  
{  
    private readonly List<T> _items = new();  

    public void Add(T item) => _items.Add(item);  

    public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();  
}  

// 使用集合表达式初始化  
CustomCollection<string> tags = ["dotnet", "csharp"];  

这里可以把它理解成:编译器看到目标类型具备集合初始化所需能力,就会为你生成相应的 Add 调用序列。

除了基础初始化,还有哪些实用形式

原始介绍里提到的几个能力,真正放到项目里时,最值得关注的是下面四种:

1. 统一而简洁的写法

  • 使用 [...] 替代 new List<T> { ... } 或 new[] { ... }
  • 同一套写法可覆盖数组、List<T>、Span<T> 等目标类型

这对于频繁写 DTO、配置项、测试数据和示例代码的人尤其友好,能明显减少模板噪音。

2. 用 .. 组合已有集合

扩展运算符适合把已有数据拼接进新集合。相比先建一个空列表再逐步 AddRange,它更接近“声明结果长什么样”。在返回结果集、构造参数列表和拼接默认值时会很好用。

3. 不只用于声明变量

集合表达式可以用于初始化集合、传递参数、返回结果,也能出现在和 LINQ、异步操作、并发逻辑相邻的代码中。它本身不是这些能力的替代品,但能让周边集合构造更紧凑。

4. 字典场景也能覆盖

除了顺序集合,文中还给出了字典初始化写法:

Dictionary<string, int> scores = ["Alice": 90, "Bob": 85]; // 等价于 new Dictionary<string, int> { ["Alice"] = 90, ["Bob"] = 85 }

这让“数组、列表、字典分别记三套初始化模板”的情况进一步减少,统一感会更强。

什么时候值得用,什么时候先别急着全量替换

如果你的项目已经升级到 .NET 8.0 并使用 C# 12.0,集合表达式很适合作为默认的新代码写法,尤其是以下情况:

集合表达式常见用法与采用判断信息图
采用场景与判断标准用场景和判断条件梳理集合表达式在项目中的推荐用法,帮助团队决定是否推广。
  • 需要频繁初始化小型集合
  • 示例代码、测试代码、配置代码较多
  • 希望数组、列表、接口类型初始化风格统一
  • 项目中已经大量使用目标类型推断

但在这些情况下,最好先局部采用,再考虑逐步推广:

  • 团队还不熟悉 C# 12 新语法
  • 代码库需要同时兼容较旧语言版本
  • 某些位置过度依赖上下文推断,可能降低一眼可读性

一个比较稳妥的判断标准是:如果 [...] 让代码更短且不牺牲理解成本,就值得用;如果必须停下来推断它最终到底是数组、列表还是别的实现类型,那就应该适当保留显式类型信息。

整体来看,集合表达式并不是改变集合能力本身,而是把“创建集合”这件事统一成了更现代的语法入口。对日常业务开发来说,它最大的价值不是炫技,而是减少样板代码、提升一致性,并让集合初始化更接近代码真正想表达的数据结构。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表