模式匹配与 switch 表达式
大约 13 分钟约 3763 字
模式匹配与 switch 表达式
简介
模式匹配(Pattern Matching)是 C# 7.0 引入并在后续版本不断增强的特性。它允许在代码中以声明式的方式检查数据的形状和内容,替代繁琐的 if-else 和类型判断。switch 表达式(C# 8.0)将传统 switch 语句转化为表达式,配合模式匹配使用更加简洁强大。
特点
基本模式
类型模式
/// <summary>
/// 类型模式 — is 操作符检查类型并提取值
/// </summary>
public string Describe(object obj)
{
// 传统方式
if (obj is string)
{
var str = (string)obj;
return $"字符串:{str},长度:{str.Length}";
}
// 模式匹配 — 类型检查 + 变量声明合一
if (obj is string s)
{
return $"字符串:{s},长度:{s.Length}";
}
if (obj is int i)
{
return $"整数:{i},平方:{i * i}";
}
if (obj is List<int> list)
{
return $"整数列表,数量:{list.Count}";
}
return "未知类型";
}属性模式
/// <summary>
/// 属性模式 — 匹配对象的属性值
/// </summary>
public decimal CalculateDiscount(Order order)
{
return order switch
{
// 匹配多个属性
{ Status: OrderStatus.VIP, TotalAmount: > 10000 } => 0.7m,
{ Status: OrderStatus.VIP } => 0.8m,
{ Status: OrderStatus.Regular, TotalAmount: > 5000 } => 0.85m,
{ Status: OrderStatus.Regular, TotalAmount: > 1000 } => 0.9m,
{ Status: OrderStatus.New } => 0.95m,
_ => 1.0m // 默认
};
}
// 嵌套属性模式
public string GetAddressInfo(User user)
{
return user switch
{
{ Address: { City: "深圳" } } => "深圳用户",
{ Address: { City: "广州" } } => "广州用户",
{ Address: null } => "无地址信息",
_ => "其他地区"
};
}switch 表达式
基本 switch 表达式
/// <summary>
/// switch 表达式 — 替代 switch 语句的简洁写法
/// </summary>
// 传统 switch 语句
public string GetSeason_Old(int month)
{
switch (month)
{
case 3: case 4: case 5: return "春季";
case 6: case 7: case 8: return "夏季";
case 9: case 10: case 11: return "秋季";
case 12: case 1: case 2: return "冬季";
default: return "无效月份";
}
}
// switch 表达式(推荐)
public string GetSeason(int month) => month switch
{
>= 3 and <= 5 => "春季",
>= 6 and <= 8 => "夏季",
>= 9 and <= 11 => "秋季",
12 or 1 or 2 => "冬季",
_ => "无效月份"
};
// HTTP 状态码映射
public string GetStatusMessage(int code) => code switch
{
200 => "OK",
201 => "Created",
204 => "No Content",
400 => "Bad Request",
401 => "Unauthorized",
403 => "Forbidden",
404 => "Not Found",
500 => "Internal Server Error",
>= 200 and < 300 => "Success",
>= 400 and < 500 => "Client Error",
>= 500 => "Server Error",
_ => "Unknown"
};元组模式
/// <summary>
/// 元组模式 — 匹配多个值的组合
/// </summary>
public string RockPaperScissors(string player1, string player2)
{
return (player1, player2) switch
{
("rock", "scissors") => "玩家1胜",
("scissors", "paper") => "玩家1胜",
("paper", "rock") => "玩家1胜",
("rock", "paper") => "玩家2胜",
("scissors", "rock") => "玩家2胜",
("paper", "scissors") => "玩家2胜",
(_, _) when player1 == player2 => "平局",
_ => "无效输入"
};
}
// 实际应用 — 跨表查询策略
public string GetQueryStrategy(string dbType, int dataSize)
{
return (dbType, dataSize) switch
{
("sqlserver", < 10000) => "直接查询",
("sqlserver", >= 10000) => "分页查询",
("mongodb", < 1000) => "单文档查询",
("mongodb", >= 1000) => "聚合管道",
("redis", _) => "键值查询",
_ => "默认查询"
};
}when 守卫条件
/// <summary>
/// when 守卫 — 添加额外条件
/// </summary>
public string ClassifyNumber(int n) => n switch
{
< 0 => "负数",
0 => "零",
> 0 and < 10 => "个位数",
>= 10 and < 100 => "两位数",
>= 100 => "三位数及以上",
};
// 在类型模式中使用 when
public string ProcessResponse(object response) => response switch
{
HttpResponseMessage { StatusCode: System.Net.HttpStatusCode.OK } r
when r.Content.Headers.ContentLength > 1000000 => "大文件响应",
HttpResponseMessage { StatusCode: System.Net.HttpStatusCode.OK } => "正常响应",
HttpResponseMessage { StatusCode: var code } => $"异常响应:{code}",
Exception ex => $"错误:{ex.Message}",
_ => "未知响应"
};列表模式(C# 11)
/// <summary>
/// 列表模式 — 匹配数组/列表的元素
/// </summary>
public string Classify(int[] numbers) => numbers switch
{
[] => "空数组",
[var single] => $"单元素:{single}",
[var a, var b] => $"两个元素:{a}, {b}",
[0, .., 0] => "首尾都是0",
[1, 2, 3] => "1-2-3 序列",
[var first, .. var middle, var last] => $"首:{first},中间{middle.Length}个,尾:{last}",
};
// 解构命令行参数
public void ParseArgs(string[] args)
{
switch (args)
{
case ["help"]:
case ["-h"]:
case ["--help"]:
ShowHelp(); break;
case ["run", var file]:
RunFile(file); break;
case ["build", var file, "-o", var output]:
BuildFile(file, output); break;
case ["test", .. var testArgs]:
RunTests(testArgs); break;
default:
Console.WriteLine("未知命令");
break;
}
}解构与模式匹配
/// <summary>
/// 解构函数配合模式匹配
/// </summary>
public class Point
{
public double X { get; }
public double Y { get; }
public Point(double x, double y) => (X, Y) = (x, y);
// 解构函数
public void Deconstruct(out double x, out double y)
{
x = X;
y = Y;
}
}
public string DescribePoint(Point point) => point switch
{
(0, 0) => "原点",
(var x, 0) => $"X轴上,x={x}",
(0, var y) => $"Y轴上,y={y}",
(var x, var y) when x == y => $"对角线上,值={x}",
(var x, var y) => $"坐标:({x}, {y})"
};高级模式组合
嵌套模式与复杂匹配
/// <summary>
/// 嵌套模式 — 多层解构匹配
/// </summary>
public record Employee(string Name, int Age, Department? Dept);
public record Department(string Name, Manager? Manager);
public record Manager(string Name, int Level);
public string EvaluateEmployee(Employee emp) => emp switch
{
{ Age: < 18 } => "未成年员工",
{ Dept: null } => "未分配部门",
{ Dept.Manager: null } => $"部门 {emp.Dept.Name} 缺少经理",
{ Dept.Manager.Level: >= 3, Dept.Name: "技术部" } => "技术部高级管理者",
{ Dept: { Name: "技术部", Manager: { Level: var level } } }
when level >= 2 => $"技术部中级管理者(L{level})",
{ Dept: var dept } when dept.Name.Length > 10 => $"{dept.Name} 部门员工",
_ => "普通员工"
};
// 深度嵌套解构
public record Order(int Id, Customer Customer, List<OrderLine> Lines);
public record Customer(string Name, Address? Address);
public record Address(string City, string Province);
public record OrderLine(string Product, int Quantity, decimal Price);
public decimal CalculateTax(Order order) => order switch
{
{ Customer.Address: null } => 0m,
{ Customer.Address.Province: "广东" } => order.Lines.Sum(l => l.Price * l.Quantity) * 0.06m,
{ Customer.Address.Province: "北京" } => order.Lines.Sum(l => l.Price * l.Quantity) * 0.05m,
_ => order.Lines.Sum(l => l.Price * l.Quantity) * 0.03m
};模式匹配中的逻辑运算
/// <summary>
/// and/or/not 模式组合运算符(C# 9+)
/// </summary>
public string ClassifyTemperature(double temp) => temp switch
{
< -40 => "极端严寒",
>= -40 and < -20 => "严寒",
>= -20 and < 0 => "寒冷",
>= 0 and < 15 => "凉爽",
>= 15 and < 28 => "舒适",
>= 28 and < 35 => "炎热",
>= 35 => "酷暑"
};
// not 模式 — 排除特定情况
public string ProcessInput(object input) => input switch
{
null => "空输入",
not null and string s => $"非空字符串: {s}",
not (int or long) => "非整数类型",
int i when i > 0 => $"正整数: {i}",
_ => "其他"
};
// 组合多种模式
public bool IsValidEmail(string email) => email switch
{
null or "" or { Length: < 5 } => false,
not { Contains: "@" } => false,
{ Contains: "@" } when email.Split('@').Length != 2 => false,
_ => true
};模式匹配性能考量
模式匹配的编译器优化
/// <summary>
/// 编译器对模式匹配的优化策略
/// </summary>
// 1. 简单类型模式 — 编译为 isinst + brfalse
void SimplePattern(object obj)
{
if (obj is string s)
{
// IL: isinst string
// IL: brfalse.s skip
// IL: stloc.s s
// 无额外开销,和手写 is + cast 等价
}
}
// 2. 常量模式 — 编译为直接的比较指令
void ConstantPattern(int value)
{
var result = value switch
{
1 => "one", // IL: ldc.i4.1 + ceq + brtrue
2 => "two", // IL: ldc.i4.2 + ceq + brtrue
_ => "other"
};
}
// 3. 关系模式 — 编译为比较 + 分支
void RelationalPattern(int value)
{
var result = value switch
{
< 0 => "negative",
>= 0 and <= 100 => "in range",
> 100 => "overflow"
};
// 编译器可能生成决策树而非线性比较
}
// 4. 属性模式 — 编译为属性访问 + 比较
void PropertyPattern(string s)
{
var result = s switch
{
{ Length: > 100 } => "long string",
{ Length: 0 } => "empty",
_ => "normal"
};
// 多次访问同一属性时,编译器可能缓存
}避免过度复杂的模式匹配
/// <summary>
/// 复杂模式匹配的可读性问题与替代方案
/// </summary>
// ❌ 过度嵌套 — 难以阅读和维护
public string BadMatch(Order order) => order switch
{
{ Customer: { Address: { City: "深圳" }, Name: var name } } when name.Length > 5
=> "深圳长名客户",
{ Customer: { Address: { Province: "广东" } }, Lines: { Count: > 10 } }
=> "广东大单客户",
// 继续嵌套...
_ => "普通客户"
};
// ✅ 先提取,再匹配 — 更清晰
public string GoodMatch(Order order)
{
var city = order.Customer?.Address?.City;
var province = order.Customer?.Address?.Province;
var lineCount = order.Lines?.Count ?? 0;
var name = order.Customer?.Name ?? "";
return (city, province, lineCount, name.Length) switch
{
("深圳", _, _, > 5) => "深圳长名客户",
(_, "广东", > 10, _) => "广东大单客户",
_ => "普通客户"
};
}
// ✅ 使用策略模式替代超长 switch
public interface IDiscountStrategy
{
bool CanApply(Order order);
decimal Calculate(Order order);
}
public class VipLargeOrderStrategy : IDiscountStrategy
{
public bool CanApply(Order order) =>
order.Customer.IsVip && order.TotalAmount > 10000;
public decimal Calculate(Order order) =>
order.TotalAmount * 0.7m;
}switch 表达式的穷尽性检查
编译器穷尽性分析
/// <summary>
/// switch 表达式的穷尽性检查与密封类
/// </summary>
// 密封类 — 编译器能确保所有子类都被覆盖(C# 8+)
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;
public record Triangle(double Base, double Height) : Shape;
// 编译器检查所有可能的子类
public double CalculateArea(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
Triangle t => 0.5 * t.Base * t.Height
// 如果遗漏 Triangle,编译器会发出警告
};
// 枚举的穷尽性检查
enum TrafficLight { Red, Yellow, Green }
public string GetAction(TrafficLight light) => light switch
{
TrafficLight.Red => "停止",
TrafficLight.Yellow => "注意",
TrafficLight.Green => "通行"
// 遗漏任何值都会有警告
};
// 使用 discard 模式显式处理未知情况
public string SafeGetAction(TrafficLight light) => light switch
{
TrafficLight.Red => "停止",
TrafficLight.Yellow => "注意",
TrafficLight.Green => "通行",
_ => "未知信号" // 即使所有枚举值都覆盖了,加上 _ 也很安全
};模式匹配与函数式编程
使用模式匹配实现表达式求值器
/// <summary>
/// 模式匹配实现简单的表达式求值器
/// </summary>
public abstract record Expr;
public record NumExpr(int Value) : Expr;
public record AddExpr(Expr Left, Expr Right) : Expr;
public record MulExpr(Expr Left, Expr Right) : Expr;
public record NegExpr(Expr Inner) : Expr;
public static class Evaluator
{
// 递归模式匹配求值
public static int Evaluate(Expr expr) => expr switch
{
NumExpr(var value) => value,
AddExpr(var left, var right) => Evaluate(left) + Evaluate(right),
MulExpr(var left, var right) => Evaluate(left) * Evaluate(right),
NegExpr(var inner) => -Evaluate(inner),
_ => throw new ArgumentException($"未知表达式类型: {expr.GetType().Name}")
};
// 模式匹配实现表达式优化(常量折叠)
public static Expr Optimize(Expr expr) => expr switch
{
// 加法恒等
AddExpr(NumExpr(0), var right) => Optimize(right),
AddExpr(var left, NumExpr(0)) => Optimize(left),
// 乘法恒等
MulExpr(NumExpr(1), var right) => Optimize(right),
MulExpr(var left, NumExpr(1)) => Optimize(left),
MulExpr(_, NumExpr(0)) => new NumExpr(0),
// 常量折叠
AddExpr(NumExpr(var a), NumExpr(var b)) => new NumExpr(a + b),
MulExpr(NumExpr(var a), NumExpr(var b)) => new NumExpr(a * b),
// 双重取反
NegExpr(NegExpr(var inner)) => Optimize(inner),
// 递归优化
AddExpr(var left, var right)
=> new AddExpr(Optimize(left), Optimize(right)),
MulExpr(var left, var right)
=> new MulExpr(Optimize(left), Optimize(right)),
NegExpr(var inner) => new NegExpr(Optimize(inner)),
// 基础情况
NumExpr _ => expr
};
}
// 使用
var expr = new AddExpr(
new MulExpr(new NumExpr(3), new NumExpr(4)),
new NegExpr(new NumExpr(2))
);
Console.WriteLine(Evaluator.Evaluate(expr)); // 10优点
缺点
常见陷阱与最佳实践
模式匹配的常见错误
/// <summary>
/// 模式匹配中的常见陷阱
/// </summary>
// 陷阱 1:模式的顺序很重要
public string Classify(int x) => x switch
{
> 0 => "正数",
> 100 => "大正数", // 永远不会匹配!> 0 已经匹配了所有正数
_ => "零或负数"
};
// 修复:从特殊到一般
public string ClassifyFixed(int x) => x switch
{
> 100 => "大正数",
> 0 => "正数",
0 => "零",
_ => "负数"
};
// 陷阱 2:类型模式的顺序
public string Process(object obj) => obj switch
{
object => "object", // 永远匹配!所有类型都是 object
string s => $"字符串: {s}", // 永远不会执行
_ => "其他"
};
// 修复:从具体到一般
public string ProcessFixed(object obj) => obj switch
{
string s => $"字符串: {s}",
int i => $"整数: {i}",
IList<int> list => $"整数列表: {list.Count}",
_ => $"其他: {obj.GetType().Name}"
};
// 陷阱 3:值类型的装箱
// switch 表达式对值类型有优化,但 object 上的模式匹配会装箱
public void BoxingTrap()
{
int value = 42;
// 以下不会装箱 — 编译器使用直接比较
var result = value switch
{
> 100 => "big",
_ => "small"
};
// 以下会装箱 — value 被提升为 object
object boxed = value;
var result2 = boxed switch
{
int i => i.ToString(), // unbox
_ => "not int"
};
}最佳实践总结
| 实践 | 说明 |
|---|---|
| 从特殊到一般 | 模式顺序决定匹配优先级,具体的放前面 |
| 避免深层嵌套 | 超过3层嵌套时,先提取变量再匹配 |
| 配合密封类 | 密封类让穷尽性检查生效 |
| 使用 when 守卫 | 复杂条件用 when 而非嵌套模式 |
| 测试边界值 | 确保边界条件(0、null、空集合)被正确处理 |
| 考虑策略模式 | 超过10个分支时,考虑用策略模式替代 |
总结
模式匹配和 switch 表达式是现代 C# 的标志性特性。掌握类型模式、属性模式、元组模式和列表模式,能让代码更简洁、更安全、更具表达力。
关键知识点
- 先明确这个主题影响的是语法层、运行时层,还是性能与可维护性层。
- 学习时要同时关注语言表面写法和编译器、JIT、GC 等底层行为。
- 真正有价值的是知道“为什么这样写”和“在什么边界下不能这样写”。
项目落地视角
- 把示例改成最小可运行样例,并观察编译输出、运行结果和异常行为。
- 如果它会进入团队代码规范,最好同步补充命名约定、禁用场景和替代方案。
- 涉及性能结论时,优先用 Benchmark 或实际热点链路验证,而不是凭感觉判断。
常见误区
- 只记语法糖,不知道底层成本。
- 把适用于小样例的写法直接搬到高并发或大对象场景里。
- 忽略框架版本、语言版本和运行时差异,导致结论失真。
进阶路线
- 继续向源码、IL、JIT 行为和 BCL 实现层深入。
- 把知识点和代码评审、性能诊断、面试复盘结合起来。
- 把同类主题做横向对比,例如值类型与引用类型、迭代器与 async 状态机、反射与 Source Generator。
适用场景
- 当你准备把《模式匹配与 switch 表达式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在需要理解语言特性、运行时行为或 API 边界时阅读。
- 当代码开始出现性能瓶颈、可维护性问题或语义歧义时,这类主题会直接影响实现质量。
落地建议
- 先写最小可运行样例,再把结论迁移到真实业务代码。
- 同时记录这个特性的收益、限制和替代方案,避免为了“高级”而使用。
- 涉及内存、并发或序列化时,最好配合调试器或基准测试验证。
排错清单
- 先确认问题属于编译期、运行期还是语义误用。
- 检查是否存在隐式转换、装箱拆箱、闭包捕获或上下文切换等隐藏成本。
- 查看异常栈、日志和最小复现代码,优先排除使用姿势问题。
复盘问题
- 如果把《模式匹配与 switch 表达式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《模式匹配与 switch 表达式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《模式匹配与 switch 表达式》最大的收益和代价分别是什么?
