引言

随着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<T> _:
  • 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 == ','

not 语法

if (obj is not null) { ... }

比传统 !(obj is null) 更直观。

4.2 关系模式(Relational Patterns)

直接用 >, <, >=, <= 表达区间

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

结合属性/类型模式:

shape switch {
Circle { Radius: >1 and <=10 } => "较合理圆",
Circle { Radius: >10 } => "过大圆",
Rectangle => "矩形",
_ => "不匹配"
};

4.3 类型模式精简

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


5. C#10.0: 扩展属性模式

支持嵌套点表达式:

之前版本:

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

C#10:

x is Person { FirstName.Length: <=5 }

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


6. C#11.0: 列表与切片模式

6.1 列表模式

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

if (arr is [1, 2, 3])
// 匹配长度为3,内容依次为1,2,3
  • [..]: 切片模式,等价于“0个或多个元素”
  • _: 忽略一个元素(discard)
[_, >0, ..]      // 至少2个元素,第二个>0
[.., <=0, _] // 至少2个元素,倒数第二个<=0且不关心最后一个

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

6.2 列表递归

可以做嵌套匹配:

bool EndsWithSingleIntList(List<List<int>> lists) => lists is [.., [_]];
// 至少一个元素,且最后一个是单元素list

6.3 支持的结构

只要类型有 Length/Count 属性和支持 [index] 则可用列表模式,如 string、数组、自定义集合(只需这两个成员)。

例如处理国别码:

switch (s) {
case [char c0, char c1]: // 即 s.Length==2
...
}

7. 模式匹配背后的实现与性能分析

7.1 不是“魔法”,而是IL优化

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

例如:

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

编译后 Equals:

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

对于一组常量匹配:

statusCode is 0x1000 or 0x1001 or 0x1002 or 0x2000 or 0x2001 or 0x3000

编译器会变换成:

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

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

️ 建议:

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


8. 常见误区与最佳实践

8.1 不要用 pattern 替代多态

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

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

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

abstract class Shape { public abstract double Area {get;} }

class Circle : Shape {
public double Radius { get; set; }
public override double Area => Radius * Radius * Math.PI;
}

为什么?

  • 可扩展性强(新子类无需修改 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 泛型化、任意表达式匹配),我们有理由期待模式匹配变得更加强大和灵活。如果你还没用过模式匹配,是时候投入生产实践试试啦!

C# 模式匹配全解:原理、用法与易错点的更多相关文章

  1. verilog disable 用法 (易错!)

    disable语句可以退出任何循环,能够终止任何begin..end块的执行,用于仿真验证中. 例如 begin:one ;i<;i=i+) begin:two ) disable one; / ...

  2. 易全解token获取

    //易全解app             string strClientID = "2016061711434943493606";             string str ...

  3. Latex排版全解

    Latex排版全解 LATEX(英语发音:/ˈleɪtɛk/ LAY-tek或英语发音:/ˈlɑːtɛk/ LAH-tek,音译“拉泰赫”),是一种基于TEX的排版系统,由美国电脑学家莱斯利•兰伯特在 ...

  4. 第48章 MDK的编译过程及文件类型全解—零死角玩转STM32-F429系列

    第48章     MDK的编译过程及文件类型全解 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.co ...

  5. RAID技术全解图解-RAID0、RAID1、RAID5、RAID100【转】

    图文并茂 RAID 技术全解 – RAID0.RAID1.RAID5.RAID100…… RAID 技术相信大家都有接触过,尤其是服务器运维人员,RAID 概念很多,有时候会概念混淆.这篇文章为网络转 ...

  6. RAID 技术全解

    图文并茂 RAID 技术全解 – RAID0.RAID1.RAID5.RAID100-- RAID 技术相信大家都有接触过,尤其是服务器运维人员,RAID 概念很多,有时候会概念混淆.这篇文章为网络转 ...

  7. Mybatis系列全解(八):Mybatis的9大动态SQL标签你知道几个?提前致女神!

    封面:洛小汐 作者:潘潘 2021年,仰望天空,脚踏实地. 这算是春节后首篇 Mybatis 文了~ 跨了个年感觉写了有半个世纪 ... 借着女神节 ヾ(◍°∇°◍)ノ゙ 提前祝男神女神们越靓越富越嗨 ...

  8. Sql Server函数全解<四>日期和时间函数

    原文:Sql Server函数全解<四>日期和时间函数   日期和时间函数主要用来处理日期和时间值,本篇主要介绍各种日期和时间函数的功能和用法,一般的日期函数除了使用date类型的参数外, ...

  9. Echarts数据可视化polar极坐标系,开发全解+完美注释

    全栈工程师开发手册 (作者:栾鹏) Echarts数据可视化开发代码注释全解 Echarts数据可视化开发参数配置全解 6大公共组件详解(点击进入): title详解. tooltip详解.toolb ...

  10. Echarts数据可视化parallel平行坐标系,开发全解+完美注释

    全栈工程师开发手册 (作者:栾鹏) Echarts数据可视化开发代码注释全解 Echarts数据可视化开发参数配置全解 6大公共组件详解(点击进入): title详解. tooltip详解.toolb ...

随机推荐

  1. 🧠ChatGPT 中文提示语大全【超全 Prompt 用法】- 已分类

    基于 awesome-chatgpt-prompts 进行分类,更方便查找自己所需,炼制适合自己的ChatGPT法宝. 工作 简历写手 我需要你写一份<2年工作经验的前端工程师>的简历,你 ...

  2. JS数组相减

    const arr1 = [1, 2, 3] const arr2 = [1, 3] const arr3 = arr1.filter(v => !arr2.includes(v)) // [2 ...

  3. AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗

    最近在研究AI Agent如何调用三方API,整理了一篇文章,分享给大家. 调用三方 API(Function Calling)不是通过提示词(Prompt)来实现的,而是通过函数调用机制(Funct ...

  4. 分享一个我遇到过的“量子力学”级别的BUG。

    你好呀,我是歪歪. 前几天在网上冲浪的时候,看到知乎上的这个话题: 一瞬间,一次历史悠久但是记忆深刻的代码调试经历,"刷"的一下,就在我的脑海中蹦出来了. 虽然最终定位到的原因令人 ...

  5. WebKit Inside: CSS 的匹配原理

    相关文章 WebKit Inside: CSS 样式表的解析 WebKit Inside: CSS 样式表的匹配时机 WebKit Inside: Acitvie 样式表 当WebView解析完所有外 ...

  6. Unsloth更快训练大模型并导出GGUF - Windows

    环境搭建 系统环境 需要Nvidia显卡,至少8G显存,且专用显存与共享显存之和大于20G 建议将非安装版的环境文件都放到非系统盘,方便重装或移植 以Windows11为例,非安装环境文件都放在 E ...

  7. Go 应用程序使用 dockerfile multi-stage 的问题

    场景重现 一个简单的go应用,准备通过docker部署,为了减少运行时的镜像和容器体积,使用了multi-stage构建: # dockerfile 大致如下 # 一级构建使用带golang环境的镜像 ...

  8. Golang 获取类型

    可以通过reflect包的TypeOf()取得某一变量或值的类型.

  9. Cannot attach the file *.mdf as database

    使用ASP.NET MVC 4 和 Entity Framwork 6(Code First)爬的一个坑,无意间把App_Data下的*.mdf文件删除了,然后程序编译正常,但是运行异常RT,留爪. ...

  10. AI时代:本地运行大模型ollama

    https://ollama.com/ 使用 Llama 2.Mistral.Gemma 和其他大型语言模型启动和运行. 支持windows,Linux,Mac. 支持的开源模型列表: Ollama ...