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

B站影视 韩国电影 2025-06-08 08:41 2

摘要:随着C#不断发展,"模式匹配"(Pattern Matching)已经成为让代码更加友好、可读和强大的核心特性。从

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

模式匹配本质上是一种表达式判定工具:用以检查一个对象是否与某种“模式”相吻合,如果吻合,还允许对其分解、绑定成员变量。这可以是类型检查、常量判断、属性结构匹配等。

传统C#写法:

if (person != && person.Age >= 18&& person is Employee
&& ((Employee)person).YearsAtCompany >5) {
// 处理逻辑
}

模式匹配写法:

if (person is Employee { Age: >= 18, YearsAtCompany: >5}) {
// 处理逻辑
}

更简洁、可读、类型安全,不需要重复显式强制转型。

C#7.0 首先支持了用is直接与以及常量对比:if (obj is ) Console.WriteLine("对象为");
if (d is Math.PI) Console.WriteLine("d 是圆周率");
if (str is"test") Console.WriteLine("str内容等于test");

注意:常量匹配仅支持编译期常量,比如数字、字符串、bool、enum、const字段和。不能直接用非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结合新模式后可以直接安全声明变量,无需后续 检查。

as后还得写if (x != )

2.3 Discard 与 var 模式

• Discard(_):匹配但忽略,用于 switch 的 default 分支或类型“只是判断,不关心值”。

• var:总是匹配成功,并引出变量。通常用于解构场景,比如 switch/case 匹配对象成员。

if (obj is var o)
Console.WriteLine(o);// 不管 obj 是否为,o 指向原始值(即使)⚠️ 这里容易误用!

不要用 var 跳过 检查。因为这样会把 值“吞掉”,造成 ReferenceError 隐患。

if (p is var _)
{// 这里仍可能是
Console.WriteLine(p.Length);// ReferenceException
}2.4 switch 语句的模式匹配

传统 switch 只能匹配简单的枚举或数字等,C#7.0 后支持以下写法:

switch (seq) {
case Array a: return a.Length;
case ICollection c: return c.Count;
case IEnumerable _: return seq.Count;
default: return0;
}

• 某类型满足条件即分支命中

• Discard case IEnumerable _:

• default处理剩余情况

只能用于 switch:case Array a when a.Length 10: return a.Length;3.1 表达式 Switch

C#8 引入 switch-表达式,极大提升 switch 可读性和函数式编程体验:

stringwhatShape = shape switch {
Circle c =>$"circle radius: {c.Radius}"
Rectangle _ =>"rectangle"
_ =>" or unknown"
};

• 匹配分支是表达式,不能有多条语句

右边直接返回值,非常适合表达式体成员:public static stringDescribe(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}"
{ } =>"非"
_ =>""
};重点说明:

{ }代表对象非。

{ Width: 10, Height: 5 }同时要求2个属性等于指定值。

{ Width: var x, Height: >5 }可以对属性用关系操作和抽取变量。

• 若对象有Deconstruct方法,可用位置/元组模式更优雅。

3.3 位置模式(Positional Patterns)

利用 Deconstruct 抽取变量:

public void Deconstruct(outdoublew, outdoubleh) => (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.1 组合模式(and/or/not)

组合条件极其强大:

c is(>='a'and 'z')or(>='A'and 'Z') or '.' or ','

等价于:

比传统更直观。4.2 关系模式(Relational Patterns)

直接用 >, =,

rate = monthlyIncome switch {
>=0and 1000=>0
5000=>10
_ =>20
};

结合属性/类型模式:

4.3 类型模式精简C#9 类型模式无需 underscore,eg:Rectangle直接写。

支持嵌套点表达式:

x is Person { FirstName: { Length: 5} }

C#10:

x is Person { FirstName.Length: 5}

让属性链判定更加直观简洁。

6.1 列表模式

让数组/集合结构模式判定成为可能:

if (arr is [123])
// 匹配长度为3,内容依次为1,2,3

[..]: 切片模式,等价于“0个或多个元素”

_: 忽略一个元素(discard)

[_, >0, ..]// 至少2个元素,第二个>0
[.., 0, _]// 至少2个元素,倒数第二个

注意:切片只能出现一次。

6.2 列表递归

可以做嵌套匹配:

boolEndsWithSingleIntList(Listint>> lists) => lists is [.., [_]];
// 至少一个元素,且最后一个是单元素list6.3 支持的结构只要类型有Length/Count属性和支持[index]则可用列表模式,如 string、数组、自定义集合(只需这两个成员)。

例如处理国别码:

许多人以为 pattern matching 是一种基于反射或者表达式树的黑魔法。实际上,编译器常常能把很多 pattern 匹配转为高效的“顺序判断”或“跳表”等机器码。

例如:

编译后 Equals:

(c >= 'a'&& c 'z') || (c >='A'&& c 'Z') || c =='.'|| c ==','

对于一组常量匹配:

statusCode is 0x1000or0x1001or0x1002or0x2000or0x2001or0x3000

编译器会变换成:

return ((uint)(statusCode -4096) 2u|| (uint)(statusCode -8192) 1u|| statusCode ==12288);

即用范围短路、减少 equals 判断次数。

⚠️ 建议:

对于性能敏感代码,请结合 Benchmark.NET 做实测对比。有时手写的判定和 pattern matching 效果类似,甚至后者更优。

8.1 不要用 pattern 替代多态

最容易见的误用是用模式匹配处理继承结构的多态行为:

// xxx
public staticdoubleArea(this Shape shape) => shape switch {
Circle c => c.Radius * c.Radius * Math.PI,
Rectangle r => r.Width * r.Height
};

事实上,应该优先用抽象基类/接口的虚方法或属性

abstract class Shape{ public abstractdoubleArea {get;} }

classCircle:Shape{
publicdoubleRadius { get; set; }
public overridedoubleArea => Radius * Radius * Math.PI;
}

为什么?

• 可扩展性强(新子类无需修改 Area 逻辑,符合开闭原则)。

• 虚调用比类型判定快。

• 更少维护负担。

8.2 Constant-Only 限制

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

8.3 避免滥用 var/discard

• 不要用 is var x跳过 检查。

• discard _只适合确实不关心值的场景,用后不要尝试访问。

8.4 List/Slice 模式性能使用[..var arr, x]这种 slice+变量捕获,编译器可能分配新数组,造成性能下降。大数据集合应谨慎。

Pattern Matching 是现代 C# 代码的“瑞士军刀”,能极大提升 if/else、switch/case 类代码的简洁性、表达力和类型安全性,在 switch 表达式等场景下优势更加明显。它不仅代码层面更美观,也能为某些条件分支提供更优指令级性能。然而,模式匹配并非多态(polymorphism)、虚方法的替代品!业务逻辑与类型分发仍建议用面向对象原则解决。

当前最大限制为:只能用常量做模式判定,变量(非编译期const)不支持,理由有 exhaustiveness 推导、避免副作用等。后续版本是否会提升,还需 C# 团队权衡设计复杂性。

未来,随着 C# 特性继续演进(如 pattern 泛型化、任意表达式匹配),我们有理由期待模式匹配变得更加强大和灵活。如果你还没用过模式匹配,是时候投入生产实践试试啦!

相信大家在开发中经常会遇到一些性能问题,苦于没有有效的工具去发现性能瓶颈,或者是发现瓶颈以后不知道该如何优化。之前一直有读者朋友询问有没有技术交流群,但是由于各种原因一直都没创建,现在很高兴的在这里宣布,我创建了一个专门交流.NET性能优化经验的群组,主题包括但不限于:

• 如何找到.NET性能瓶颈,如使用APM、dotnet tools等工具

• .NET框架底层原理的实现,如垃圾回收器、JIT等等

• 如何编写高性能的.NET代码,哪些地方存在性能陷阱

希望能有更多志同道合朋友加入,分享一些工作中遇到的.NET问题和宝贵的分析优化经验。目前一群已满,现在开放二群。可以加我vx,我拉你进群: ls1075 另外也创建了QQ Group: 687779078,欢迎大家加入。

来源:opendotnet

相关推荐