很多 C# 项目里,分支判断一多就容易堆出冗长的 if-else、类型转换和空值判断。模式匹配的价值,不只是把语法写短,而是把“判断什么、提取什么、返回什么”收束到一处。下面按演进、基础写法、高级模式和实际场景拆开讲,方便你判断哪些代码值得改,哪些地方改了以后会更清楚。
模式匹配是怎么从语法增强变成核心能力的
模式匹配自 C# 7.0 引入后,几乎每个大版本都在补强。早期它主要解决类型检查和变量提取的问题,到了后续版本,已经能覆盖对象结构判断、范围匹配、逻辑组合以及列表形态判断,足以替代大量手工分支。
它最常见的三个入口分别是:
is表达式:用于检查并在匹配成功时直接声明变量。switch语句:适合保留传统分支结构,但用更强的匹配条件组织逻辑。switch表达式:C# 8.0引入,更适合把“输入映射到输出”的逻辑写成表达式。
如果按版本看,演进路径大致如下:
| 版本 | 新增特性 |
|---|---|
| C# 7.0 | 基本模式匹配:is 表达式、switch 语句增强 |
| C# 8.0 | 递归模式、属性模式、位置模式、switch 表达式 |
| C# 9.0 | 类型模式简化、逻辑模式(and/or/not)、关系模式 |
| C# 10.0 | 扩展属性模式 |
| C# 11.0 | 列表模式、切片模式 |
这条演进线很重要,因为它决定了你能不能把原本分散在多个判断块里的条件,压缩成一个结构清晰、可读性更高的匹配表达式。
先掌握三类最常用的基础模式
如果你刚从传统分支切换到模式匹配,最值得先掌握的是声明模式、类型模式和常量模式。这三类覆盖了日常代码里最常见的判断场景,也是后面高级模式的基础。
声明模式:判断类型的同时直接拿到变量
声明模式最大的意义,是省掉“先判断、再强转、再使用”的重复动作。对于需要立刻使用匹配结果的代码,这种写法通常比传统写法更直观。
// 检查类型并声明新变量
if (obj is string str)
{
Console.WriteLine($"字符串长度: {str.Length}");
}
// 传统方式对比
if (obj is string)
{
string str = (string)obj;
Console.WriteLine($"字符串长度: {str.Length}");
}
这里的差异不只是少写一行强制转换,更关键的是把“类型成立后才能使用这个变量”的关系表达清楚了。
类型模式:只关心类型归类时更合适
如果你只需要知道对象属于哪一类,而不急着把它提取为新变量,类型模式会更简洁。它很适合做输入归类,尤其适合搭配 switch 表达式使用。
// 只检查类型,不声明变量
if (obj is string)
{
Console.WriteLine("这是一个字符串");
}
// 在 switch 表达式中使用
var result = obj switch
{
string => "它是字符串",
int => "它是整数",
_ => "其他类型"
};
当分支的目标是“把不同输入映射为不同结果”时,这种写法通常会比多层 if-else 更整齐。
常量模式:把特定值判断收束到统一入口
常量模式用于匹配固定值,包括数字、字符串和 null。它适合把零散的相等判断合并进统一的分支结构里。
// 检查特定值
if (value is 5) { /* 值等于5 */ }
if (value is null) { /* 值为null */ }
if (value is "hello") { /* 字符串比较 */ }
// 在 switch 中
string message = value switch
{
0 => "零",
1 => "一",
null => "空值",
_ => "其他值"
};
这类模式最直接的收益是减少散落的判空和相等比较,让分支处理集中在一个入口里维护。
高级模式能替代哪些复杂分支
从 C# 8.0 到 C# 11.0,模式匹配真正拉开差距的地方,在于它开始能描述对象结构、位置关系、范围条件和集合形态。很多原本要靠嵌套 if、临时变量和多段判断才能写清楚的逻辑,在这里都能收拢。
属性模式:直接按对象结构做判断
属性模式适合处理 DTO、订单、配置对象这类层级清晰的数据。与其逐层判空、逐个属性比较,不如直接写出你要匹配的结构。
// 检查对象属性
if (person is { Name: "John", Age: > 18 })
{
Console.WriteLine("John 已成年");
}
// 嵌套属性模式
if (order is { Customer: { Address: { City: "Beijing" } } })
{
Console.WriteLine("北京客户的订单");
}
// C# 10 扩展属性模式
if (order is { Customer.Address.City: "Beijing" })
{
Console.WriteLine("北京客户的订单(简化写法)");
}
C# 10.0 的扩展属性模式进一步减少了嵌套层级,适合结构深但目标属性明确的判断场景。
位置模式:适合元组和可解构对象
位置模式常见于元组,也能用于实现了 Deconstruct 的自定义类型。它的优势是把“数据形态”和“分支结果”直接绑定到一起。
// 元组解构和匹配
var point = (x: 5, y: 10);
var quadrant = point switch
{
(0, 0) => "原点",
(var x, var y) when x > 0 && y > 0 => "第一象限",
(var x, var y) when x < 0 && y > 0 => "第二象限",
(var x, var y) when x < 0 && y < 0 => "第三象限",
(var x, var y) when x > 0 && y < 0 => "第四象限",
_ => "在坐标轴上"
};
这类写法适合坐标、状态组合、返回值拆分等场景,尤其适合“多个值共同决定结果”的逻辑。
逻辑模式和关系模式:把范围与组合条件写得更集中
C# 9.0 带来的 and、or、not 以及关系模式,是模式匹配真正适合业务判断的重要一步。它让“范围”“排除”“并列条件”都能直接出现在模式本身里。
// 使用逻辑运算符组合模式
bool IsValid(object obj) => obj is not null and (string or int);
// 复杂逻辑组合
var category = value switch
{
> 0 and < 10 => "小的正数",
< 0 or > 100 => "超出范围",
not null => "非空值",
_ => "其他"
};
// 使用关系运算符
string GetSize(int value) => value switch
{
< 0 => "负数",
>= 0 and < 10 => "小",
>= 10 and < 100 => "中",
>= 100 => "大"
};
// 与类型模式结合
string Describe(object obj) => obj switch
{
int i when i < 0 => "负整数",
int i when i > 0 => "正整数",
int => "零",
_ => "其他"
};
当你面对的是区间判断、状态归类、输入过滤时,这两类模式通常比手写连续条件更利于维护。不过也要注意,如果条件本身过长,过度堆叠在一个表达式里同样会影响可读性。
列表模式和字符串模式:面向序列内容做匹配
C# 11.0 的列表模式和切片模式,补上了集合场景的表达能力。对于数组、列表、字符序列这类输入,现在可以直接按开头、结尾、中间片段来写判断。
// 匹配数组或列表的特定模式
int[] numbers = { 1, 2, 3, 4 };
var result = numbers switch
{
[1, 2, 3, 4] => "精确匹配",
[1, 2, ..] => "以1,2开头",
[.., 3, 4] => "以3,4结尾",
[1, .. var middle, 4] => $"以1开头4结尾,中间有{middle.Length}个元素",
[] => "空数组",
_ => "其他模式"
};
// 切片模式
if (numbers is [var first, .. var rest])
{
Console.WriteLine($"第一个元素: {first}, 剩余元素: {rest.Length}");
}
string text = "Hello, World";
if (text is ['H','e','l','l','o', ..])
{
Console.WriteLine("以Hello开头");
}
// 与Span<char>模式匹配结合
ReadOnlySpan<char> span = text.AsSpan();
if (span is ['H','e','l','l','o', ..])
{
Console.WriteLine("匹配Span");
}
这类写法对协议头判断、命令前缀识别、轻量文本解析尤其有用。相比手工下标访问,它更接近“按结构描述输入”。
自定义类型和表达式树也能用模式匹配
如果类型支持 Deconstruct,就可以自然接入位置模式;如果你在做元编程或规则分析,也可能会遇到表达式树中的模式匹配。
// 通过 Deconstruct 方法支持位置模式
public class Point
{
public int X { get; }
public int Y { get; }
public void Deconstruct(out int x, out int y)
{
x = X;
y = Y;
}
}
// 现在可以对 Point 使用位置模式
var quadrant = point switch
{
(0, 0) => "原点",
(var x, var y) when x > 0 && y > 0 => "第一象限",
// ...
};w
Expression<Func<object, bool>> expr = o => o switch
{
int i when i > 0 => true,
string s when s.Length > 5 => true,
_ => false
};
// 解析表达式树
if (expr.Body is SwitchExpression switchExpr)
{
foreach (SwitchCase @case in switchExpr.Cases)
{
// 处理每个case
}
}
这部分不一定是日常业务代码的主战场,但它说明模式匹配已经不只是便捷写法,而是能嵌入类型设计和表达式分析流程中的语言能力。
在业务代码里,模式匹配最适合落在哪些地方
模式匹配真正有价值的地方,不在演示代码,而在业务分支。凡是“输入结构清楚、输出分支明确”的逻辑,通常都值得优先考虑。
数据验证:把失败条件集中到一个出口
订单校验、请求参数校验、状态检查都很适合用 switch 表达式重写。这样做的好处是每个失败条件都以独立模式出现,顺序和优先级也一目了然。
public ValidationResult Validate(Order order)
{
return order switch
{
null => ValidationResult.Failure("订单不能为空"),
{ TotalAmount: <= 0 } => ValidationResult.Failure("金额必须大于0"),
{ Customer: null } => ValidationResult.Failure("客户信息缺失"),
{ Items: [] } => ValidationResult.Failure("订单项不能为空"),
{ Status: OrderStatus.Cancelled } => ValidationResult.Failure("订单已取消"),
_ => ValidationResult.Success()
};
}
和传统的多段 if 相比,这种方式更容易维护,也更方便后续补充新规则。
API 响应处理:按状态和内容统一归类
接口响应处理也是模式匹配非常典型的应用点。状态码、错误对象、返回数据是否为空,原本往往分散在多层判断里,现在可以直接按响应结构写。
public string HandleResponse(ApiResponse response)
{
return response switch
{
{ StatusCode: 200, Data: not null } => "成功",
{ StatusCode: 404 } => "资源未找到",
{ StatusCode: >= 400 and < 500 } => "客户端错误",
{ StatusCode: >= 500 } => "服务器错误",
{ Error: not null } => $"错误: {response.Error.Message}",
_ => "未知响应"
};
}
这类代码的关键收益是规则集中。以后你要增加 401、429 或特定错误结构的处理,只需要在原有分支表中追加规则即可。
什么时候该用,什么时候别硬套
模式匹配确实能让代码更简洁,但“更短”不总是“更好”。判断它是否值得上模式匹配,可以看三个标准。
- 如果你在做类型判断、空值分流、固定值映射,优先考虑基础模式。
- 如果你在处理对象结构、范围区间、集合形态,高级模式通常比嵌套分支更清楚。
- 如果一个模式表达式已经长到需要读者反复停顿,应该考虑拆分方法、提炼变量,或者退回更朴素的写法。
从实际工程经验看,模式匹配最适合承担“规则明确、结构稳定、分支清晰”的逻辑。它不是要消灭所有 if-else,而是把本来就在做结构判断的代码,写成更接近业务意图的样子。
















