🎯 模式匹配速查 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 3x is string s
C# 8属性模式、元组模式、switch 表达式、丢弃 _{ Name: "A" }(a,b)L02
C# 9关系模式、and/or/not>= 18 and <= 65L08
C# 10扩展属性模式{ Address.City: "上海" }L09
C# 11列表模式arr is [1, .., 9]L10
C# 12+(无新模式,巩固现有)

所有模式一览

模式写法匹配条件版本
常量Constant42, "hello", nullEquals 相等(null 特殊处理)C# 7
类型TypeCircle c, Rectangle是该类型(null 自动排除)C# 7
varVarvar 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
组合Combinatorand, or, not模式逻辑组合(关键字,非运算符)C# 9
否定Negatednot 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 是关键字,不是运算符。它们是模式系统的一部分,只存在于 isswitch 的模式上下文中。优先级: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 + 索引器
stringLength + 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 也匹配一切但绑定变量。二者的共同点是都覆盖所有情况。

相关速查 & 课程