🎯 模式匹配速查 Pattern Matching
C# 7 引入 · C# 8~12 持续增强 · 用"形状"描述数据而非用步骤
一、什么是"模式"What Is a Pattern
模式 = 一个表达式,描述"我期待数据长什么样"。
它做三件事:① 测试这个值是否符合条件 ② 符合时提取感兴趣的部分到变量 ③ 不满足则走下一分支,绝不抛异常。
你早就用过模式的雏形:
// if + == 就是最简单的手工模式——但 == 只能做"等于"
if (status == 3) { ... }
// 真正的模式可以做更多:类型检查 + 变量绑定,一步完成
if (obj is string s && s.Length > 0) { ... }
// └──────模式──────┘ └─when─┘
is 是模式入口,switch 是模式分发器。所有模式都可以放在 is 后面(返回 bool),也可以放在 switch { } 表达式/语句里作为分支条件。
三个"模式",中文极易混淆:
① Pattern(模式匹配)= C# 语法,is/switch 里的形状描述 → 本速查
② Design Pattern(设计模式)= GoF 23 种,软件工程概念 → 无关
③ Regular Expression(正则表达式)= 文本匹配 DSL → 无关
模式版本演进
| 版本 | 新增 | 能做什么 | 对应课程 |
| C# 7 | 常量模式、类型模式、var 模式 | x is 3、x is string s | — |
| C# 8 | 属性模式、元组模式、switch 表达式、丢弃 _ | { Name: "A" }、(a,b) | L02 |
| C# 9 | 关系模式、and/or/not | >= 18 and <= 65 | L08 |
| C# 10 | 扩展属性模式 | { Address.City: "上海" } | L09 |
| C# 11 | 列表模式 | arr is [1, .., 9] | L10 |
| C# 12+ | (无新模式,巩固现有) | — | — |
所有模式一览
| 模式 | 写法 | 匹配条件 | 版本 |
| 常量Constant | 42, "hello", null | Equals 相等(null 特殊处理) | C# 7 |
| 类型Type | Circle c, Rectangle | 是该类型(null 自动排除) | C# 7 |
| varVar | var x | 匹配一切 + 绑定(含 null) | C# 7 |
| 丢弃Discard | _ | 匹配一切,不绑定 | C# 8 |
| 属性Property | { Name: "A", Age: >= 18 } | 属性形状匹配(AND 递归) | C# 8 |
| 元组Tuple | ("rock", "paper") | 多值同时匹配,每位可嵌套子模式 | C# 8 |
| 关系Relational | > 0, <= 100 | 大小比较(作用于 CompareTo/IComparable) | C# 9 |
| 组合Combinator | and, or, not | 模式逻辑组合(关键字,非运算符) | C# 9 |
| 否定Negated | not null, not > 100 | 反转任意子模式 | C# 9 |
| 扩展属性Extended Property | { A.B: 1 } | 嵌套属性直接打点(C# 10 语法糖) | C# 10 |
| 列表List | [1, .., 9] | 集合长度 + 元素模式(鸭子类型) | C# 11 |
Switch 表达式Switch Expression (C# 8)
旧:switch 语句
string result;
switch (x)
{
case 1: result = "一"; break;
case 2: result = "二"; break;
default: result = "?"; break;
}
return result;
新:switch 表达式
return x switch
{
1 => "一",
2 => "二",
_ => "?",
};
穷尽性:switch 表达式必须覆盖所有可能值。必须有 _ 兜底(除非穷举所有枚举/布尔值)。编译时检查。
关系模式Relational Patterns (C# 9)
将 < > <= >= 用于模式匹配——底层调用 IComparable.CompareTo,不是运算符重载。
// is 表达式
if (score is >= 90 and <= 100) { } // 90~100
if (temp is < 0 or > 40) { } // 异常温度
// switch 表达式——干净的分段逻辑
string Grade(int score) => score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F",
};
// 和属性模式嵌套
order switch
{
{ Total: >= 1000, IsVip: true } => 0.3m,
{ Total: >= 1000 } => 0.2m,
{ Total: >= 500 } => 0.1m,
_ => 0m,
};
关系模式有穷尽性盲区。编译器无法推断 >= 90 和 >= 80 合在一起是否覆盖了所有整数——所以仍需要 _ 兜底。
组合模式Combinator Patterns — and / or / not (C# 9)
and/or/not 是关键字,不是运算符。它们是模式系统的一部分,只存在于 is 和 switch 的模式上下文中。优先级:not > and > or。
// and:同时满足(交集)
x is >= 0 and <= 100 // 0 ≤ x ≤ 100
// or:满足其一(并集)
c is 'a' or 'e' or 'i' or 'o' or 'u' // 元音字母
// not:否定(补集)
obj is not null // is not null——NRT 时代的标准写法
value is not > 100 // ≤ 100(含负数)
// 嵌套——括号控制优先级
status is ((>= 200 and < 300) or (== 304)) // 2xx 或 304
// 典型范式:switch 表达式 + 关系模式 + 组合模式
static string Describe(int age) => age switch
{
< 0 => "无效",
>= 0 and < 12 => "儿童",
>= 12 and < 18 => "青少年",
>= 18 and < 60 => "成年人",
>= 60 and not > 120 => "老年人",
_ => "不可能",
};
属性模式Property Pattern (C# 8) & 扩展属性模式 (C# 10)
// 基础:多个属性 = AND 关系
order switch
{
{ IsVip: true, Total: >= 1000 } => 0.2m,
{ IsVip: true } => 0.1m,
{ Total: >= 500 } => 0.05m,
_ => 0m,
};
// 嵌套:属性的值本身也是模式(递归)
p switch
{
{ Address: { City: "北京" } } => "北京居民",
_ => "其他",
};
// C# 10 扩展属性模式——省一层嵌套
p switch
{
{ Address.City: "北京" } => "北京居民", // 直接打点!
_ => "其他",
};
// 空属性模式:{ } 匹配所有非 null 实例
obj is { } // 等价于 obj is not null
obj is { } notNull // 绑定到 notNull 变量
// 类型 + 属性组合:先查类型,再解构属性
shape switch
{
Circle { Radius: > 100 } => "大圆",
Rectangle { Width: var w, Height: var h } when w == h => "正方形",
Rectangle => "矩形",
null => "空",
_ => "未知",
};
Rectangle 不写 { } 也是类型模式——自动排除 null。写 Rectangle { } 等同于纯类型模式。但如果想匹配"是 Rectangle 且某属性满足条件",必须加 { }。
元组模式Tuple Pattern (C# 8)
// 多维决策表——替代层层 if-else
string GameResult(string p1, string p2) => (p1, p2) switch
{
("rock", "scissors") => "胜",
("rock", "paper") => "负",
("rock", "rock") => "平",
_ => "?",
};
// 每个位置可以是任意子模式
(x, y) switch
{
(0, 0) => "原点",
(_, 0) => "X 轴",
(> 0, > 0) => "第一象限",
_ => "其他",
};
// 元组 + 属性模式混用
(user, product) switch
{
({ IsVip: true }, _) => "VIP 不限购",
(_, { Stock: 0 }) => "缺货",
({ Age: < 18 }, { Category: "成人" }) => "年龄不符",
_ => "可购买",
};
列表模式List Patterns (C# 11)
模式匹配扩展到集合——用 [...] 描述集合的长度和元素形状。支持任何有 Length/Count + 可访问索引器的类型(鸭子类型),包括 T[]、List<T>、Span<T>、string。
完整语法
// ① 精确匹配——长度和元素都相等
arr is [1, 2, 3] // {1,2,3} → true; {1,2} → false; {1,2,3,4} → false
// ② 元素使用任意子模式
arr is [1, >= 10, <= 100] // 第一个=1, 第二个≥10, 第三个≤100
arr is [not 0, ..] // 第一项不是 0
// ③ 切片模式(..)——匹配任意数量元素(含零个)
arr is [1, ..] // 第一个是 1,后面随便
arr is [.., 9] // 最后一个是 9
arr is [1, .., 9] // 首 1 尾 9,中间任意(含零个)
arr is [1, .. { Length: 2 }, 9] // 1, [任意两个], 9
// ④ var 捕获——提取匹配到的元素
if (arr is [var first, .. var middle, var last])
// first=arr[0], middle=arr[1..^1], last=arr[^1]
// ⑤ 嵌套模式——元素是对象时匹配属性
orders is [{ Status: "New" }, .., { Total: >= 1000 }]
switch 表达式中的列表模式
static string Describe(int[] arr) => arr switch
{
[] => "空",
[var x] => $"单元素 {x}",
[var x, var y] => $"两元素 {x}, {y}",
[1, .., 1] => "首尾都是 1",
[not 0, ..] => "首非零",
_ => "其他",
};
// 实战:REST 路由
static string Route(string[] seg) => seg switch
{
["api", "users"] => "用户列表",
["api", "users", var id] => $"用户 {id}",
["api", ..] => "其他 API",
_ => "不匹配",
};
支持的类型 & 编译器判断
| 类型 | 支持? | 原因 |
T[] | ✅ | 原生数组 |
List<T> | ✅ | 有 Count + 索引器 |
Span<T> / ReadOnlySpan<T> | ✅ | C# 11 新增,有 Length + 索引器 |
string | ✅ | 有 Length + char 索引器 |
| 自定义集合 | ✅ | 有 Length/Count + this[int] 或 Slice 方法 |
when 子句When Clause
执行顺序:模式先匹配 → when 再过滤 → when 失败则继续试下一个分支(不抛异常)。
| 场景 | 模式能做吗 | 用 when |
| 匹配常量 | ✅ | 不需要 |
| 范围比较 | ✅ 关系模式 | 不需要 |
| null 检查 | ✅ null / not null | 不需要 |
| 比较两个变量 | ❌ | when w == h |
| 调用方法 | ❌ | when s.StartsWith("ERR") |
| 集合计数/索引访问 | ❌(C# 11 列表模式可做部分) | when list.Count > 5 |
// when 必要:条件涉及方法调用
s switch
{
null => "null",
string x when x.Trim().Length == 0 => "空白",
string x when x.StartsWith("ERR") => "错误",
_ => "普通",
};
// 陷阱:when 失败→跳过,不是报错
score switch
{
>= 90 when score <= 100 => "A", // score=110: 模式命中, when 失败→跳过→进下一行
>= 80 => "B",
_ => "C",
};
模式的本质:可递归嵌套
所有模式可以嵌套——这是模式匹配超越 if-else 的根本原因:
// 一个表达式检查多层结构——每层都可以有测试 + 提取
if (obj is Order {
Status: "Paid" or "Shipped",
Items: [_, .., { Price: >= 1000 }],
Customer: { Address.City: "上海" or "北京" }
}) { ... }
// └─类型──┘└────属性────────────┘└────列表───────────┘└────扩展属性──────────────┘
// 每一个 {}、[]、not、and 都是一个子模式——组合成深层结构描述
关键事实
为什么 or/and/not 是关键字不是运算符?
|/& 已是位运算符——对值做运算产出新值。模式匹配是描述形状,不是计算值。用关键字彻底隔离优先级歧义。! 不能当 not 因为 x is !null 语义奇怪。
分支匹配顺序:从上到下,不是"最匹配"。永远把更具体的分支放上面。
switch 表达式的 _ 是丢弃模式,不是 default。它匹配一切但丢弃值。var x 也匹配一切但绑定变量。二者的共同点是都覆盖所有情况。
相关速查 & 课程