模式匹配是C# 7.0之后逐步引入并在后续版本中不断增强的语言特性,它让开发者可以用声明式的方式对变量的类型、结构和值进行检查与提取。传统写法里我们常先用is判断类型,再强制转换,最后读属性;模式匹配把这些步骤压缩进一个表达式,既减少临时变量,也避免转换失败导致的运行时异常。

一、is类型模式与声明模式
最基础的用法是is类型模式,它可以在判断对象类型的同时声明一个新变量,如果匹配成功该变量自动完成转换,作用域仅限于后续逻辑块。相比先写if (obj is string)再(string)obj的写法,声明模式少一次类型转换操作,也避免了转换与判断不一致的风险。
下面代码展示如何用is模式处理一个未知类型的集合元素。注意当匹配失败时不进入分支,变量也不会被赋值,因此不存在空引用或无效转换问题。
object data = "hello pattern";
if (data is string text)
{
Console.WriteLine("字符串长度为: " + text.Length);
}
else if (data is int number)
{
Console.WriteLine("整数平方为: " + (number * number));
}
else
{
Console.WriteLine("未知类型");
}
声明模式也支持常量模式,例如data is null或data is 0,编译器会优化成对应的相等判断。在热路径上这种写法比调用Equals方法略快,同时语义更清楚。
当只需要判断而不需要提取值时,可以直接写is类型不加变量名。但多数业务场景下我们更关心内容,因此声明模式使用频率更高。
二、switch表达式与类型模式
C# 8.0引入的switch表达式把传统switch语句变成可返回值的表达式,配合类型模式可以替代复杂的if else链。它要求覆盖所有情况或使用弃元模式_兜底,编译器会检查完整性,漏写分支会在编译期报错。
以下示例用switch表达式计算不同形状的面积。每个case都是一个模式,右侧是表达式主体,整体返回一个double。这种写法没有break,也不会意外穿透,比语句版switch安全得多。
abstract class Shape { }
class Circle : Shape { public double Radius { get; set; } }
class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } }
double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
null => 0,
_ => throw new ArgumentException("未知形状")
};
switch表达式的求值顺序自上而下,第一个匹配的模式生效。因此具体类型要写在通用类型之前,否则会被宽泛模式拦截。若把_放在最前,后面所有分支都成了死代码,编译器会给出警告。
在需要多个条件组合时,可以在模式后加when子句做额外过滤。when内部可以使用任意布尔逻辑,但过于复杂的when会降低可读性,此时建议拆分成独立方法。
三、属性模式与位置模式
属性模式允许在switch中直接解构对象的属性并匹配具体值。比如对包含X、Y坐标的点做区域判断,旧写法要反复访问point.X,属性模式则把条件内联,逻辑一目了然。
下面代码用属性模式识别坐标系中的象限。每个分支同时检查X和Y的符号,匹配成功才执行对应表达式,不需要手动写逻辑与运算。
class Point { public int X { get; set; } public int Y { get; set; } }
string Quadrant(Point p) => p switch
{
{ X: > 0, Y: > 0 } => "第一象限",
{ X: < 0, Y: > 0 } => "第二象限",
{ X: < 0, Y: < 0 } => "第三象限",
{ X: > 0, Y: < 0 } => "第四象限",
{ X: 0, Y: 0 } => "原点",
_ => "坐标轴上"
};
位置模式依赖Deconstruct方法,把对象拆成有序参数再匹配。它适合元组式数据结构,写起来比属性模式更紧凑,但要求类型显式提供解构函数,否则编译失败。
属性模式与位置模式可以嵌套。例如匹配一个圆且半径大于十的情况,可写成Circle c and { Radius: > 10 },逻辑与用and连接,或用or表示多选,这种组合在验证输入时非常实用。
四、元组模式与递归模式
元组模式直接对多个值做联合匹配,常用于游戏状态机或权限判断。它不需要定义新类型,用括号包住多个变量即可,编译器会生成高效的相等比较。
以下例子根据角色与等级返回不同提示。两个维度同时参与匹配,分支清晰,比嵌套if更易维护。
string GetTip(string role, int level) => (role, level) switch
{
("admin", >= 10) => "高级管理员",
("admin", _) => "普通管理员",
("user", >= 5) => "活跃用户",
("user", _) => "新用户",
_ => "访客"
};
递归模式指模式本身可以包含子模式,例如列表模式匹配数组首个元素。C#对列表模式的支持让处理集合头部变得直观,可以用来写简单的解析器或命令分发器。
看一段匹配数组开头内容的代码。模式["get", var path]表示数组第一个元素是字符串get,第二个元素捕获到path变量,长度不符则不匹配。这种写法替代了手工索引访问,越界风险交由编译器处理。
string Parse(string[] cmd) => cmd switch
{
["get", var path] => "读取: " + path,
["set", var path, var val] => "写入: " + path + "=" + val,
[] => "空命令",
_ => "未知命令"
};
五、性能与最佳实践
模式匹配在编译期大多被翻译成常规类型判断与虚方法调用,没有反射开销。is声明模式比is加转换少一次类型检查,在循环内可观察到微小但稳定的提升。switch表达式生成的IL通常比等价if链更紧凑。
使用时应注意不要在一个switch里堆砌十几条带when的复杂分支,那样会抵消可读性优势。当业务逻辑庞大时,可把部分模式逻辑抽到独立函数,或用字典委托替代。另外,对值类型使用模式匹配不会产生装箱,对引用类型也只是普通判等,不必担心隐藏分配。
最后建议从局部重构开始,把最啰嗦的if else块改成switch表达式,再逐步引入属性与元组模式。团队统一风格后,代码评审成本会明显下降,新成员也能更快理解分支意图。
C#_Pattern_Matchingswitch表达式类型模式修改时间:2026-08-09 18:09:43