C# 的模式匹配在此前已经能处理类型、属性和 switch 分支,但一旦逻辑落到“这个数组是不是以某个值开头”“这组参数是不是恰好三段”“中间那一段要不要取出来”时,代码往往又回到长度判断、索引访问和一串 if-else。C# 11 加入的列表模式,正是为这类场景补上了更直观的写法。下面就从语法、匹配规则到常见示例拆开看,帮助你判断它适合替代哪些旧代码,以及什么时候能真正提升可读性。
列表模式到底解决了什么问题
列表模式属于模式匹配机制的一部分,它不是新的集合类型,而是一种专门针对集合结构的匹配写法。它可以对集合的元素数量、顺序,以及每个位置上的内容进行检查。
在 C# 10 之前,模式匹配已经支持 switch 表达式、类型模式、属性模式等能力,但对列表或序列的结构判断并不够直接。到了 C# 11,列表模式(List Patterns)让这类判断可以直接写进模式本身。
- 按顺序匹配数组、
Span<T>、IList<T>等列表。 - 检查长度、特定元素,甚至解构其中的值。
- 结合
switch、is、when写出更紧凑的声明式代码。
列表模式是模式匹配的扩展,不是新的集合类型。
语法怎么写,匹配时遵循哪些规则
列表模式使用方括号 [] 来描述要匹配的结构,基本形式如下:
[ element-pattern-1, element-pattern-2, ... ]
例如,下面这段代码要求列表必须恰好是三个元素,且顺序精确为 1, 2, 3:
// 检查列表是否恰好包含三个元素:1, 2, 3
if (list is [1, 2, 3])
{
Console.WriteLine("列表包含精确序列 1, 2, 3");
}
列表模式中的元素模式可以继续复用现有的模式匹配能力,包括:
- 常量,如
1、"abc" - 变量,如
var x - 类型模式,如
int x - 通配符
_ - 切片模式
..,这是C# 11新增的重要能力 - 嵌套模式,如
{}、[]
它可以出现在这些位置:
is表达式switch表达式或语句- 带条件判断的匹配逻辑中
实际匹配时,需要注意三条核心规则:
- 匹配顺序严格一致,列表模式不是无序匹配。
- 元素数量必须满足模式要求;如果希望放宽长度限制,需要用切片模式
..。 - 可用于支持
Length或Count属性并提供索引器的类型,例如IEnumerable、数组、Span<T>、IList<T>等。
从基础写法看懂列表模式
如果你以前习惯先判断长度,再一个个取索引比较,那么下面这些示例基本就是列表模式最直接的替代方式。

精确匹配
int[] numbers = { 1, 2, 3 };
if (numbers is [1, 2, 3])
Console.WriteLine("Exact match!");
只有当数组是
{1,2,3}且长度为 3 时才匹配。列表模式的实战落点这一节更适合用结构图呈现列表模式如何替代分散的条件分支。
这类写法最适合固定长度、固定顺序的输入验证。它的优势不在于功能更强,而在于把“长度必须为 3 且三个元素分别是什么”直接写在一行模式里。
通配符 _
if (numbers is [1, _, 3])
Console.WriteLine("Middle element can be anything");
忽略第二个元素,只要求首尾固定。
当你只关心部分位置,其他位置不必参与判断时,_ 会比手动读取索引更清楚。
切片模式 ..
切片模式是列表模式里最实用的一部分,它能把“长度不固定”的场景纳入模式匹配。
匹配以 1 开头的任意长度数组:
if (numbers is [1, ..])
Console.WriteLine("Starts with 1");
匹配以 3 结尾的数组:
if (numbers is [.., 3])
Console.WriteLine("Ends with 3");
同时检查开头和结尾,中间元素数量不限:
if (numbers is [1, .., 3])
Console.WriteLine("Starts with 1 and ends with 3");
这几种写法非常适合处理命令参数、协议头尾、日志片段或带前后缀的数据结构。
变量绑定
if (numbers is [1, .. var middle, 3])
Console.WriteLine($"Middle elements count: {middle.Length}");
使用 var middle 捕获中间切片。
这里不仅做了结构判断,还把中间那一段直接绑定到了变量 middle。相比先判断再 Skip、再切片,这种写法更接近“声明你要什么”。
类型模式
object[] items = { "hello", 42, 99 };
if (items is ["hello", int x, ..])
Console.WriteLine($"Second element is int: {x}");
结合类型模式和切片。
这说明列表模式并不只是比值,它也能结合类型判断和变量提取,在处理 object[] 或混合输入时尤其有用。
空集合匹配
if (arr is [])
{
Console.WriteLine("Array is empty");
}
空集合判断原本也常见,但用列表模式表达会更统一,尤其在一整组 switch 分支里,[] 的可读性比单独判断长度更好。
几个更接近实战的用法
理解列表模式的价值,关键不在语法本身,而在它如何替代分散的控制流逻辑。下面这几类示例更能体现它的实际用途。
嵌套列表模式
object[] data = [1, [2, 3], 4];
string result = data switch
{
[1, [2, 3], 4] => "Nested match: [1, [2, 3], 4]",
_ => "No match"
};
Console.WriteLine(result); // 输出: Nested match: [1, [2, 3], 4]
当数据本身包含嵌套结构时,列表模式可以一层套一层地描述期望结构。这在解析语法树、嵌套消息、树状配置片段时很有代表性。
结合 switch 表达式
int[] nums = { 1, 2, 3, 4 };
string result = nums switch
{
[1, 2, 3, 4] => "Exact 1-2-3-4",
[1, ..] => "Starts with 1",
[.., 4] => "Ends with 4",
[] => "Empty list",
_ => "Other"
};
Console.WriteLine(result);
这里的好处是,集合分支规则被集中定义在一个位置,而不是拆散在多段 if 中。对于规则明确、分支有限的逻辑,这通常比传统写法更容易维护。
斐波那契检测
int[] fib = { 0, 1, 1, 2, 3, 5, 8 };
if (fib is [0, 1, .. var rest])
Console.WriteLine($"Fibonacci sequence with {rest.Length + 2} elements");
这个例子虽然没有完整验证斐波那契数列,但很好地展示了“先匹配固定头部,再接收剩余部分”的思路。它适合用于识别特征前缀、协议版本头或固定格式开头的数据。
解析命令行参数
string[] args = { "add", "user", "Alice" };
var command = args switch
{
["add", "user", var name] => $"Add user {name}",
["remove", "user", var name] => $"Remove user {name}",
[] => "No command",
_ => "Invalid"
};
Console.WriteLine(command);
这是列表模式非常典型的落地场景。相比手动检查 args.Length、args[0]、args[1],这里的意图几乎一眼可见:不同命令对应不同结构。
带条件的匹配
if (numbers is [var first, .., var last] && first < last)
Console.WriteLine($"First < Last ({first} < {last})");
列表模式擅长先把结构和变量提取出来,再继续追加条件判断。这样既能保留模式匹配的简洁性,也不会牺牲业务规则表达能力。
哪些场景最值得用列表模式
列表模式并不是要替代所有集合操作,它最适合的是“我想判断集合是否符合某种结构,并顺便取出其中关键值”的情况。
- 数据验证:检查输入集合是否符合预期结构。
- 数据提取:从集合中提取特定元素到变量,减少手动索引操作。
- 简化控制流:替代复杂的
if-else或局部循环判断。 - 递归或不定长处理:结合切片模式处理嵌套或长度不固定的集合。
如果你的逻辑本质上是在描述“这个列表应该长什么样”,那列表模式通常就是合适的表达方式;如果你需要大量遍历、聚合、筛选或数值计算,它就不是核心工具,仍应交给循环、LINQ 或专门的数据处理代码。
从可读性角度看,C# 11 的列表模式最有价值的地方,在于把集合结构判断从命令式步骤还原成声明式描述。代码短只是表象,真正的收益是:条件、顺序、长度和提取规则终于能写在同一处,读者不用再来回追索引和分支。










