圣罩 发表于 昨天 11:08

C# 模式匹配全解:原理、用法与易错点

引言

随着C#不断发展,"模式匹配"(Pattern Matching)已经成为让代码更加友好、可读和强大的核心特性。从 C# 7.0 初次引入,到 C# 11的能力扩展,模式匹配为处理类型判断、属性解构、集合匹配等提供了简洁、高效且类型安全的表达方式。它不仅能让 if/switch 等控制结构变得“声明式”,还能带来性能提升。在这篇文章里,我们将深入剖析 C 的所有模式匹配语法和用法,追踪其演变,讲清一些容易混淆和误用的地方,让大家能了解模式匹配本质。
1. 什么是模式匹配

模式匹配本质上是一种表达式判定工具:用以检查一个对象是否与某种“模式”相吻合,如果吻合,还允许对其分解、绑定成员变量。这可以是类型检查、常量判断、属性结构匹配等。
传统C#写法:
if (person != null && person.Age >= 18 && person is Employee
    && ((Employee)person).YearsAtCompany > 5) {
    // 处理逻辑
}模式匹配写法:
if (person is Employee { Age: >= 18, YearsAtCompany: > 5 }) {
    // 处理逻辑
}更简洁、可读、类型安全,不需要重复显式强制转型。
2. C#7.0 初代模式匹配特性

2.1 Null 模式与常量模式

C#7.0 首先支持了用is直接与null以及常量对比:
if (obj is null) Console.WriteLine("对象为null");
if (d is Math.PI) Console.WriteLine("d 是圆周率");
if (str is "test") Console.WriteLine("str内容等于test");注意:常量匹配仅支持编译期常量,比如数字、字符串、bool、enum、const字段和null。不能直接用非const变量或表达式。
这个限制为何存在?


[*]编译器要保证 switch 和 if 全覆盖检查,如果允许变量参与,则难推导 exhaustiveness(全覆盖)。
[*]也可避免不可预期的副作用与逻辑混乱(比如变量运行期改变等)。
2.2 类型模式与变量捕获

类型模式允许 you not only 判断类型,还能直接捕获变量:
if (shape is Circle circle1)
    Console.WriteLine($"圆的半径为 {circle1.Radius}");甚至可以链式判断:
if (shape is Rectangle r && r.Width == r.Height)
    Console.WriteLine("正方形");非常适用于临时变量创建、减少强制类型转换(显式as/cast)代码杂音。
is vs as?


[*]is 结合新模式后可以直接安全声明变量,无需后续 null 检查。
[*]as 后还得写 if (x != null)。
2.3 Discard 与 var 模式


[*]Discard(_):匹配但忽略,用于 switch 的 default 分支或类型“只是判断,不关心值”。
[*]var:总是匹配成功,并引出变量。通常用于解构场景,比如 switch/case 匹配对象成员。
if (obj is var o)
   Console.WriteLine(o); // 不管 obj 是否为null,o 指向原始值(即使null)⚠️ 这里容易误用!

不要用 var 跳过 null 检查。因为这样会把 null 值“吞掉”,造成 NullReferenceError 隐患。
if (p is var _)
{             // 这里仍可能是 null
    Console.WriteLine(p.Length); // NullReferenceException
}2.4 switch 语句的模式匹配

传统 switch 只能匹配简单的枚举或数字等,C#7.0 后支持以下写法:
switch (seq) {
    case Array a: return a.Length;
    case ICollection<T> c: return c.Count;
    case IEnumerable<T> _: return seq.Count();
    default: return 0;
}

[*]某类型满足条件即分支命中
[*]Discard case IEnumerable _:
[*]default处理剩余情况
switch 的 when 子句

when 只能用于 switch:
case Array a when a.Length < 10: return a.Length;无法用于 if!
3. C#8.0: Switch表达式、属性、位置、元组模式

3.1 表达式 Switch

C#8 引入 switch-表达式,极大提升 switch 可读性和函数式编程体验:
string whatShape = shape switch {
    Circle c   => $"circle radius: {c.Radius}",
    Rectangle _=> "rectangle",
    _            => "null or unknown"
};

[*]匹配分支是表达式,不能有多条语句
=>右边直接返回值,非常适合表达式体成员:
public static string Describe(this Shape s) => s switch { ... };3.2 属性模式 (Property Patterns)

可直接针对属性表达式判定
if (shape is Circle { Radius: 1.0 })
    Console.WriteLine("单位圆");

whatShape = shape switch {
    Rectangle { Width: 10, Height: 5 } => "10x5 矩形",
    Rectangle { Width: var x, Height: var y } => $"rect: {x}x{y}",
    { } => "非null",
    _ => "null"
};重点说明:


[*]{ } 代表对象非null。
[*]{ Width: 10, Height: 5 } 同时要求2个属性等于指定值。
[*]{ Width: var x, Height: >5 } 可以对属性用关系操作和抽取变量。
[*]若对象有Deconstruct方法,可用位置/元组模式更优雅。
3.3 位置模式(Positional Patterns)

利用 Deconstruct 抽取变量:
public void Deconstruct(out double w, out double h) => (w, h) = (Width, Height);

shape switch {
    Rectangle(var x, var y) => $"矩形尺寸:{x}x{y}",
    ...
}

[*]用于解构类对象(类似元组)
[*]写法类似解构元组
3.4 元组模式

处理多个参数的模式组合:
(c1, c2, c3) switch {
    (Color.Blue, Color.White, Color.Red) => "France",
    (Color.Green, Color.White, Color.Red) => "Italy",
    _ => "Unknown"
};

[*]有效解决“复杂组合条件冗长”的老大难。
[*]更直观表达多变量匹配,不需层层嵌套。
4. C#9.0: 组合、括号及关系模式

4.1 组合模式(and/or/not)

组合条件极其强大:
c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ','结合属性/类型模式:
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '.' || c == ','4.3 类型模式精简

C#9 类型模式无需 underscore,eg:Rectangle 直接写。
5. C#10.0: 扩展属性模式

支持嵌套点表达式:
之前版本:
if (obj is not null) { ... }事实上,应该优先用抽象基类/接口的虚方法或属性:
rate = monthlyIncome switch {
    >=0 and <1000 => 0,
    <5000 => 10,
    _ => 20
};为什么?

[*]可扩展性强(新子类无需修改 Area 逻辑,符合开闭原则)。
[*]虚调用比类型判定快。
[*]更少维护负担。
8.2 Constant-Only 限制

只有编译期常量才能用于模式匹配,如果需要支持运行时变量,需采用传统判定。
8.3 避免滥用 var/discard


[*]不要用 is var x 跳过 null 检查。
[*]discard _ 只适合确实不关心值的场景,用后不要尝试访问。
8.4 List/Slice 模式性能

使用 [..var arr, x] 这种 slice+变量捕获,编译器可能分配新数组,造成性能下降。大数据集合应谨慎。
9. 小结与展望

Pattern Matching 是现代 C# 代码的“瑞士军刀”,能极大提升 if/else、switch/case 类代码的简洁性、表达力和类型安全性,在 switch 表达式等场景下优势更加明显。它不仅代码层面更美观,也能为某些条件分支提供更优指令级性能。然而,模式匹配并非多态(polymorphism)、虚方法的替代品!业务逻辑与类型分发仍建议用面向对象原则解决。
当前最大限制为:只能用常量做模式判定,变量(非编译期const)不支持,理由有 exhaustiveness 推导、避免副作用等。后续版本是否会提升,还需 C# 团队权衡设计复杂性。
未来,随着 C# 特性继续演进(如 pattern 泛型化、任意表达式匹配),我们有理由期待模式匹配变得更加强大和灵活。如果你还没用过模式匹配,是时候投入生产实践试试啦!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: C# 模式匹配全解:原理、用法与易错点