模式匹配
C#模式匹配与switch表达式
一、学习目标
本文以 MIT 6.102 Software Construction、Stanford CS193p、CMU 15-150 Functional Programming 的模式匹配教学水准为参照,对 C# 模式匹配(Pattern Matching)进行系统性、形式化与工程化的深度剖析。阅读完毕后,读者应能达成以下 Bloom 认知层级目标:
| 层级 | 目标描述 | 具体可观测行为 |
|---|---|---|
| Remember(记忆) | 复述 C# 模式的语法分类、引入版本与基本语义 | 列出至少 8 种模式类型及其对应的 C# 版本 |
| Understand(理解) | 解释模式匹配的归约规则、穷举性检查与编译期翻译 | 说明 is { Length: > 0 } 如何被翻译为属性访问与比较指令 |
| Apply(应用) | 在企业级代码中使用 switch 表达式、属性模式、位置模式重构分支逻辑 | 实现一个基于模式匹配的订单状态机与异常分类器 |
| Analyze(分析) | 对比 C# 模式匹配与 ML 系语言(F#、Haskell、Scala)、Rust match 的差异 | 解释为何 C# 的 switch 不默认穷举而 F# 默认穷举 |
| Evaluate(评价) | 评估 when 子句、弃元模式、嵌套模式对可读性与性能的影响 | 在 if-else 链与 switch 表达式之间做出有依据的选择 |
| Create(创造) | 设计基于活动模式(active patterns)与表达式树的领域 DSL | 实现一个基于规则引擎的折扣计算器与 AST 解释器 |
本文假设读者已掌握 C# 基础语法、泛型、LINQ、记录类型与可空引用类型。
二、历史动机与发展脉络
2.1 问题背景:if-else 与 as 噟的噪声
在 C# 7 之前,处理异构数据的标准方式是 is + 类型转换或 as + null 检查:
// C# 1.0 - 6.0 的典型样板代码
object obj = GetObject();
if (obj is string) {
var s = (string)obj;
if (s.Length > 0) {
Console.WriteLine(s);
}
} else if (obj is int) {
var i = (int)obj;
Console.WriteLine(i * 2);
}
这段代码存在三类噪声:
- 类型检测与类型转换分离:
is后必须再次(string)转换; - 临时变量污染外层作用域:
s、i在if外仍可见; - 嵌套深度与逻辑复杂度耦合:每加一个条件就多一层缩进。
Anders Hejlsberg 团队借鉴 F#、Scala、Haskell 的模式匹配思想,从 C# 7.0 开始引入”模式(pattern)“概念:一个模式是描述数据形状的谓词,匹配时同时完成判断与提取两件事。
2.2 C# 7.0(2017):模式匹配的开端
C# 7.0 引入了三类基础模式:
- 常量模式:
is null、is 5、is "hello"; - 类型模式:
is int i(声明变量); var模式:is var x(总是匹配,捕获值)。
同时 switch 语句支持模式 case:
switch (obj) {
case int i when i > 0: Console.WriteLine($"正整数: {i}"); break;
case string s: Console.WriteLine(s); break;
case null: Console.WriteLine("null"); break;
}
2.3 C# 8.0(2019):switch 表达式与属性模式
C# 8.0 引入了两项革命性改进:
- switch 表达式:
switch成为表达式而非语句,可出现在赋值右侧; - 属性模式:
is { Length: > 0 }、is { Name: "Alice" }; - 位置模式:基于
Deconstruct的元组式匹配; - 元组模式:
(x, y) switch { (0, 0) => ... }; - 递归模式:模式可嵌套任意深度。
2.4 C# 9.0(2020):逻辑模式与关系模式
C# 9.0 引入了三类组合模式:
and/or/not逻辑模式:is not null and (int or double);- 关系模式:
is > 0、is >= 10 and < 100; - 括号模式:用括号显式分组优先级;
- 弃元模式:
_显式表示”忽略”。
2.5 C# 10.0(2021):扩展属性模式
C# 10 引入扩展属性模式,使嵌套属性访问更简洁:
// C# 9 写法
if (order is { Customer: { VipLevel: >= 3 } }) { ... }
// C# 10 写法(扩展属性模式)
if (order is { Customer.VipLevel: >= 3 }) { ... }
2.6 C# 11.0(2022):列表模式
C# 11 引入列表模式,可对数组与 List<T> 进行结构化匹配:
int[] arr = { 1, 2, 3, 4, 5 };
if (arr is [1, 2, .., 5]) { /* 以 1,2 开头,5 结尾 */ }
if (arr is [var first, .. var middle, var last]) { /* 提取首尾与中间 */ }
2.7 C# 12 / 13(2023-2024):切片模式与反射模式
C# 12 完善列表模式的切片语义;C# 13 引入对 params ReadOnlySpan<T> 与模式结合的支持,并对反射场景下的 is 性能做了优化。同时社区与 ECMA-334 v6 草案开始讨论活动模式(active patterns)与模式绑定的标准化方向。
2.8 演进时间线
| 版本 | 年份 | 关键模式 |
|---|---|---|
| C# 7.0 | 2017 | 常量、类型、var 模式;switch 语句模式 case |
| C# 8.0 | 2019 | switch 表达式、属性、位置、元组、递归模式 |
| C# 9.0 | 2020 | and/or/not、关系、括号模式 |
| C# 10.0 | 2021 | 扩展属性模式 |
| C# 11.0 | 2022 | 列表模式、切片模式 |
| C# 12.0 | 2023 | 列表切片语义完善 |
| C# 13.0 | 2024 | ref struct 模式支持、性能优化 |
三、形式化定义
3.1 ECMA-334 与形式文法
ECMA-334 第 8.21 节定义模式的形式文法(简化版):
每个模式 在输入值 上求值,结果为匹配(matched)或不匹配(not matched)。匹配时可绑定若干变量。
3.2 模式匹配的归约规则
模式匹配的语义可由归约规则 定义。设环境 为变量绑定集合,输入值 与模式 的匹配关系记为 (在 下 匹配 ,扩展环境为 )。
常量模式:
类型模式:
属性模式(简化):
逻辑模式:
3.3 穷举性(Exhaustiveness)
一个 switch 表达式/语句是穷举的当且仅当对所有可能的输入值至少有一个 arm 匹配。C# 编译器对穷举性进行保守近似:
- 对枚举:要求覆盖所有枚举值(或含
_); - 对
bool:要求同时覆盖true与false(或含_); - 对引用类型:编译器不强制穷尽 null 检查,但可空上下文会警告;
- 对元组与位置模式:递归检查子模式。
设输入类型 的取值空间为 ,arm 模式 覆盖的子集为 ,穷尽性条件为:
C# 编译器在不能证明穷尽时报告 CS8509 警告(switch 表达式)。
3.4 不可达性(Unreachability)
一个 arm 是不可达的当且仅当它覆盖的子集被前序 arm 完全覆盖:
编译器报告 CS0162 警告。这与穷尽性是互补的两个静态性质。
四、理论推导与原理解析
4.1 模式匹配的编译期翻译
C# 编译器将模式匹配翻译为等价的 if-else 与类型检查代码。例如:
if (obj is string s && s.Length > 0) { ... }
被翻译为:
if (obj is string && ((string)obj).Length > 0) {
string s = (string)obj;
...
}
switch 表达式则翻译为嵌套 if-else 或基于哈希的跳转表,取决于 arm 数量与模式复杂度。
4.2 类型模式与 isinst 指令
类型模式 is T x 在 IL 层面翻译为 isinst T 指令(CIL):若对象可转型为 则压栈转型后引用,否则压栈 null。其开销等价于一次方法表遍历,对接口转型需遍历接口映射表。
形式化:
4.3 属性模式的求值复杂度
属性模式 is { P1: Q1, P2: Q2 } 对每个子模式顺序求值。设属性访问平均开销 ,子模式求值平均开销 :
C# 编译器会做短路优化:一旦某子模式不匹配即跳过剩余子模式,故平均开销小于上界。
4.4 列表模式的实现机制
列表模式 is [a, b, .., c] 通过 Length 属性与索引器实现:
// is [a, b, .., c] 被翻译为
if (list.Length >= 3) {
a = list[0];
b = list[1];
c = list[list.Length - 1];
}
对 Span<T> 与 ReadOnlySpan<T>,编译器生成 Slice 与索引访问,避免装箱。复杂度 , 为模式中显式位置数。
4.5 switch 表达式的代码生成策略
C# 编译器根据 arm 数量与模式类型选择代码生成策略:
- 跳转表(jump table):纯常量模式 + 整数输入,生成
switchIL 指令,; - 哈希查找(hash lookup):纯常量模式 + 字符串输入,生成
Dictionary<string, int>查找, 平均; - 顺序
if-else:含类型/属性/关系模式,按 arm 顺序匹配, 最坏; - 二分查找:纯关系模式且有序时,编译器可生成二分搜索,。
设 arm 数为 ,不同策略的复杂度对比:
| 策略 | 时间复杂度 | 适用场景 |
|---|---|---|
| 跳转表 | 整数常量 | |
| 哈希查找 | 平均 | 字符串常量 |
顺序 if-else | 类型/属性/关系 | |
| 二分查找 | 有序关系 |
4.6 模式匹配与代数数据类型
C# 模式匹配在概念上对应 ML 系语言的代数数据类型(ADT)匹配。F# 的判别联合(discriminated union):
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
let area s =
match s with
| Circle(r) -> Math.PI * r * r
| Rectangle(w, h) -> w * h
C# 通过 record + 模式匹配模拟:
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;
double Area(Shape s) => s switch {
Circle(var r) => Math.PI * r * r,
Rectangle(var w, var h) => w * h,
};
但 C# 不强制穷尽性检查(仅警告),F# 默认强制。这是 C# 与 ML 系语言在类型系统设计哲学上的核心差异。
五、代码示例(企业级 production-ready)
5.1 项目结构
FandexPatternMatching/
├── FandexPatternMatching.csproj
├── Program.cs
├── Domain/
│ ├── Order.cs
│ ├── OrderEvent.cs
│ └── Discount.cs
├── Handlers/
│ ├── OrderEventHandler.cs
│ ├── DiscountCalculator.cs
│ └── ExceptionClassifier.cs
├── Expressions/
│ ├── Expr.cs
│ └── ExprEvaluator.cs
└── Validators/
└── InputValidator.cs
5.2 csproj 配置(.NET 8 / C# 12)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
</ItemGroup>
</Project>
5.3 领域模型:订单与事件
// Domain/Order.cs —— C# 10 / .NET 6
namespace FandexPatternMatching.Domain;
/// <summary>
/// 订单状态枚举。所有状态在 switch 中必须被覆盖。
/// </summary>
public enum OrderStatus {
Pending,
Paid,
Shipped,
Delivered,
Cancelled,
Refunded
}
/// <summary>
/// 订单实体。使用 record 提供基于值的相等性与解构。
/// </summary>
public record Order(
string OrderId,
decimal Amount,
OrderStatus Status,
Customer Customer,
DateTime CreatedAt) {
public bool IsLargeOrder => Amount > 10000m;
}
public record Customer(
string Id,
string Name,
int VipLevel,
string City);
// Domain/OrderEvent.cs —— C# 11 / .NET 7
namespace FandexPatternMatching.Domain;
/// <summary>
/// 订单事件判别联合(用抽象 record + 具体子类模拟 ADT)。
/// 每个事件携带不同字段,由模式匹配消费。
/// </summary>
public abstract record OrderEvent {
public record Created(string OrderId, decimal Amount, DateTime At) : OrderEvent;
public record Paid(string OrderId, string PaymentMethod, decimal PaidAmount, DateTime At) : OrderEvent;
public record Shipped(string OrderId, string TrackingNumber, string Carrier, DateTime At) : OrderEvent;
public record Cancelled(string OrderId, string Reason, DateTime At) : OrderEvent;
public record Refunded(string OrderId, decimal RefundAmount, string Reason, DateTime At) : OrderEvent;
}
/// <summary>
/// 折扣策略抽象。
/// </summary>
public abstract record Discount {
public record None : Discount;
public record Percentage(decimal Rate) : Discount;
public record FixedAmount(decimal Amount) : Discount;
public record Tiered(decimal BaseAmount, decimal ExtraRate) : Discount;
}
5.4 事件处理器:基于模式的事件分发
// Handlers/OrderEventHandler.cs —— C# 12 / .NET 8
namespace FandexPatternMatching.Handlers;
using FandexPatternMatching.Domain;
/// <summary>
/// 订单事件处理器。基于模式匹配分发到不同处理逻辑。
/// 使用 switch 表达式实现"函数即查找表"风格。
/// </summary>
public sealed class OrderEventHandler {
private readonly ILogger<OrderEventHandler> _logger;
public OrderEventHandler(ILogger<OrderEventHandler> logger) => _logger = logger;
/// <summary>
/// 处理单个事件,返回处理结果摘要。
/// 模式顺序重要:更具体的模式应放在前面。
/// </summary>
public string Handle(OrderEvent evt) => evt switch {
// 嵌套属性模式 + 关系模式:大额订单创建
OrderEvent.Created { Amount: > 10000, At: var at } =>
LogAndReturn($"[大额] 订单创建 {evt.OrderId} 金额超 1 万 @ {at:O}"),
// 普通订单创建
OrderEvent.Created { OrderId: var id, Amount: var amt } =>
LogAndReturn($"订单创建 {id} 金额 {amt:C}"),
// 信用卡支付 + 金额匹配
OrderEvent.Paid { PaymentMethod: "credit_card", PaidAmount: var paid } =>
LogAndReturn($"信用卡支付完成 {paid:C}"),
// 其他支付方式
OrderEvent.Paid { PaymentMethod: var method, PaidAmount: var paid } =>
LogAndReturn($"{method} 支付完成 {paid:C}"),
// 顺丰快递特殊处理
OrderEvent.Shipped { Carrier: "SF", TrackingNumber: var tn } =>
LogAndReturn($"顺丰已发货 单号 {tn}"),
// 其他承运商
OrderEvent.Shipped { Carrier: var c, TrackingNumber: var tn } =>
LogAndReturn($"{c} 已发货 单号 {tn}"),
// 取消原因分类
OrderEvent.Cancelled { Reason: var r } when r.Contains("库存") =>
LogAndReturn($"缺货取消 {r}"),
OrderEvent.Cancelled { Reason: var r } =>
LogAndReturn($"订单取消 {r}"),
// 退款
OrderEvent.Refunded { RefundAmount: var amt, Reason: var r } =>
LogAndReturn($"退款 {amt:C} 原因 {r}"),
// 穷尽性兜底(理论上不会触发,因为已覆盖所有子类型)
_ => throw new InvalidOperationException($"未知事件类型: {evt.GetType().Name}")
};
private string LogAndReturn(string message) {
_logger.LogInformation("{Message}", message);
return message;
}
}
5.5 折扣计算器:规则引擎风格
// Handlers/DiscountCalculator.cs —— C# 11 / .NET 8
namespace FandexPatternMatching.Handlers;
using FandexPatternMatching.Domain;
/// <summary>
/// 折扣计算器。使用属性模式 + 逻辑模式实现规则引擎。
/// 规则按优先级排列,首个匹配规则生效。
/// </summary>
public sealed class DiscountCalculator {
/// <summary>
/// 根据订单与折扣策略计算最终金额。
/// </summary>
public decimal Calculate(Order order, Discount discount) => (order, discount) switch {
// VIP 5 级 + 大额 + 百分比折扣 -> 额外加 5%
({ Customer.VipLevel: >= 5, IsLargeOrder: true }, Discount.Percentage(var rate)) =>
order.Amount * (1 - rate - 0.05m),
// VIP 3 级以上 + 百分比折扣
({ Customer.VipLevel: >= 3 }, Discount.Percentage(var rate)) =>
order.Amount * (1 - rate),
// 北京用户 + 固定金额折扣
({ Customer.City: "北京" }, Discount.FixedAmount(var amt)) =>
Math.Max(0, order.Amount - amt),
// 阶梯折扣
({ IsLargeOrder: true }, Discount.Tiered(var base, var extra)) =>
(order.Amount - base) * (1 - extra) + base * 0.9m,
// 无折扣
(_, Discount.None) => order.Amount,
// 默认情况
_ => order.Amount
};
/// <summary>
/// 根据订单特征自动推荐折扣策略。
/// </summary>
public Discount Recommend(Order order) => order switch {
{ Customer.VipLevel: >= 5, IsLargeOrder: true } =>
new Discount.Tiered(5000m, 0.10m),
{ Customer.VipLevel: >= 3 } =>
new Discount.Percentage(0.08m),
{ Customer.City: "北京" or "上海" } =>
new Discount.FixedAmount(50m),
{ IsLargeOrder: true } =>
new Discount.Percentage(0.05m),
_ => new Discount.None()
};
}
5.6 异常分类器:when 子句的应用
// Handlers/ExceptionClassifier.cs —— C# 12 / .NET 8
namespace FandexPatternMatching.Handlers;
using System.Net;
/// <summary>
/// HTTP 异常分类器。根据状态码与异常类型分类错误级别。
/// </summary>
public sealed class ExceptionClassifier {
public ErrorLevel Classify(Exception ex) => ex switch {
// 5xx 服务端错误
HttpRequestException { StatusCode: >= HttpStatusCode.InternalServerError } =>
ErrorLevel.Critical,
// 4xx 客户端错误
HttpRequestException { StatusCode: >= HttpStatusCode.BadRequest } =>
ErrorLevel.Warning,
// 超时与取消
TimeoutException or OperationCanceledException =>
ErrorLevel.Transient,
// 网络异常(when 子句补充条件)
IOException io when io.Message.Contains("network") =>
ErrorLevel.Transient,
// 业务异常
BusinessException { Code: var code } when code.StartsWith("BIZ-") =>
ErrorLevel.Business,
// 未知异常
_ => ErrorLevel.Unknown
};
public enum ErrorLevel { Critical, Warning, Transient, Business, Unknown }
}
public class BusinessException(string code, string message) : Exception(message) {
public string Code { get; } = code;
}
5.7 表达式树求值器:递归模式
// Expressions/Expr.cs —— C# 10 / .NET 6
namespace FandexPatternMatching.Expressions;
/// <summary>
/// 算术表达式 AST。用 record 模拟代数数据类型。
/// </summary>
public abstract record Expr {
public record Lit(double Value) : Expr;
public record Var(string Name) : Expr;
public record Add(Expr Left, Expr Right) : Expr;
public record Sub(Expr Left, Expr Right) : Expr;
public record Mul(Expr Left, Expr Right) : Expr;
public record Div(Expr Left, Expr Right) : Expr;
public record Neg(Expr Operand) : Expr;
public record IfZero(Expr Cond, Expr Then, Expr Else) : Expr;
}
// Expressions/ExprEvaluator.cs —— C# 12 / .NET 8
namespace FandexPatternMatching.Expressions;
/// <summary>
/// 表达式求值器。递归模式匹配 + 环境查找。
/// </summary>
public sealed class ExprEvaluator {
private readonly Dictionary<string, double> _env = new();
public void SetVar(string name, double value) => _env[name] = value;
/// <summary>
/// 求值。模式匹配天然递归。
/// </summary>
public double Eval(Expr expr) => expr switch {
Expr.Lit { Value: var v } => v,
Expr.Var { Name: var n } => _env.TryGetValue(n, out var v)
? v
: throw new InvalidOperationException($"未定义变量: {n}"),
Expr.Add { Left: var l, Right: var r } => Eval(l) + Eval(r),
Expr.Sub { Left: var l, Right: var r } => Eval(l) - Eval(r),
Expr.Mul { Left: var l, Right: var r } => Eval(l) * Eval(r),
// 除零保护
Expr.Div { Right: Expr.Lit { Value: 0 } } =>
throw new DivideByZeroException("除数为零"),
Expr.Div { Left: var l, Right: var r } => Eval(l) / Eval(r),
Expr.Neg { Operand: var o } => -Eval(o),
// 条件表达式:递归嵌套模式
Expr.IfZero { Cond: var c, Then: var t, Else: var e } =>
Math.Abs(Eval(c)) < 1e-10 ? Eval(t) : Eval(e),
_ => throw new InvalidOperationException($"未知表达式: {expr}")
};
/// <summary>
/// 简化表达式(常量折叠)。展示模式匹配在变换中的应用。
/// </summary>
public Expr Simplify(Expr expr) => expr switch {
Expr.Add { Left: Expr.Lit { Value: 0 }, Right: var r } => Simplify(r),
Expr.Add { Left: var l, Right: Expr.Lit { Value: 0 } } => Simplify(l),
Expr.Add { Left: Expr.Lit(var a), Right: Expr.Lit(var b) } => new Expr.Lit(a + b),
Expr.Mul { Left: Expr.Lit { Value: 0 }, Right: _ } => new Expr.Lit(0),
Expr.Mul { Left: _, Right: Expr.Lit { Value: 0 } } => new Expr.Lit(0),
Expr.Mul { Left: Expr.Lit { Value: 1 }, Right: var r } => Simplify(r),
Expr.Mul { Left: var l, Right: Expr.Lit { Value: 1 } } => Simplify(l),
Expr.Mul { Left: Expr.Lit(var a), Right: Expr.Lit(var b) } => new Expr.Lit(a * b),
Expr.Neg { Operand: Expr.Neg { Operand: var inner } } => Simplify(inner),
Expr.Neg { Operand: Expr.Lit(var v) } => new Expr.Lit(-v),
// 其他情况递归处理子表达式
Expr.Add { Left: var l, Right: var r } =>
new Expr.Add(Simplify(l), Simplify(r)),
Expr.Sub { Left: var l, Right: var r } =>
new Expr.Sub(Simplify(l), Simplify(r)),
Expr.Mul { Left: var l, Right: var r } =>
new Expr.Mul(Simplify(l), Simplify(r)),
Expr.Div { Left: var l, Right: var r } =>
new Expr.Div(Simplify(l), Simplify(r)),
Expr.Neg { Operand: var o } => new Expr.Neg(Simplify(o)),
Expr.IfZero { Cond: var c, Then: var t, Else: var e } =>
new Expr.IfZero(Simplify(c), Simplify(t), Simplify(e)),
_ => expr
};
}
5.8 输入校验器:组合模式与 not
// Validators/InputValidator.cs —— C# 11 / .NET 8
namespace FandexPatternMatching.Validators;
using FandexPatternMatching.Domain;
/// <summary>
/// 订单输入校验器。使用 not + and + or 组合模式。
/// </summary>
public sealed class InputValidator {
public ValidationResult Validate(Order order) => order switch {
// 空 ID
{ OrderId: null or "" or " " } =>
new ValidationResult(false, "订单 ID 不能为空"),
// 金额非法
{ Amount: <= 0 } =>
new ValidationResult(false, "金额必须为正"),
// VIP 等级越界
{ Customer.VipLevel: < 0 or > 10 } =>
new ValidationResult(false, "VIP 等级必须在 0-10 之间"),
// 创建时间未来
{ CreatedAt: var t } when t > DateTime.UtcNow.AddMinutes(5) =>
new ValidationResult(false, "创建时间不能是未来"),
// 已取消订单不能有金额
{ Status: OrderStatus.Cancelled, Amount: > 0 } =>
new ValidationResult(false, "已取消订单金额应为 0"),
// 所有检查通过
_ => new ValidationResult(true, "校验通过")
};
/// <summary>
/// 使用 not 简化条件。
/// </summary>
public bool IsValidCustomer(Customer c) => c is not null and {
Name: not null and not "",
VipLevel: >= 0 and <= 10,
City: "北京" or "上海" or "深圳" or "广州"
};
}
public record ValidationResult(bool IsValid, string Message);
5.9 列表模式应用
// Handlers/ListPatternHandler.cs —— C# 11 / .NET 8
namespace FandexPatternMatching.Handlers;
/// <summary>
/// 列表模式应用:处理分页参数、解析命令行参数。
/// </summary>
public static class ListPatternHandler {
/// <summary>
/// 解析命令行参数。展示列表模式的切片用法。
/// </summary>
public static (string Command, string[] Args) ParseArgs(string[] args) => args switch {
[] => ("help", Array.Empty<string>()),
["help"] => ("help", Array.Empty<string>()),
["version"] => ("version", Array.Empty<string>()),
// 单命令 + 单参数
["get", var key] => ("get", new[] { key }),
// set 命令必须带键值对
["set", var key, var value] => ("set", new[] { key, value }),
["set", ..] => throw new ArgumentException("set 需要 key value 两个参数"),
// list 命令支持 --filter 与 --limit
["list", "--filter", var f, "--limit", var l] =>
("list", new[] { "--filter", f, "--limit", l }),
["list", "--limit", var l, "--filter", var f] =>
("list", new[] { "--filter", f, "--limit", l }),
// 默认
[var cmd, .. var rest] => (cmd, rest.ToArray())
};
/// <summary>
/// 用列表模式识别常见序列模式。
/// </summary>
public static string ClassifySequence(int[] seq) => seq switch {
[] => "空序列",
[var single] => $"单元素: {single}",
// 升序对
[var a, var b] when a < b => $"升序对: {a} < {b}",
[var a, var b] when a > b => $"降序对: {a} > {b}",
[var a, var b] => $"等值对: {a} = {b}",
// 等差数列
[var a, var b, var c] when b - a == c - b => $"等差三元组: {a}, {b}, {c}",
// 斐波那契起始
[0, 1, ..] => "斐波那契风格起始",
// 首尾相同
[var first, .., var last] when first == last => $"首尾相同: {first}",
// 一般情况
[var first, .., var last] => $"首 {first} 尾 {last}"
};
}
六、跨语言对比
6.1 与 Java 模式匹配对比
Java 16 引入 instanceof 模式匹配,Java 21 引入 switch 模式匹配。Java 的设计更保守:
| 维度 | C# | Java |
|---|---|---|
| 引入版本 | C# 7.0 (2017) | Java 16/21 (2021/2023) |
| 类型模式 | is int i | instanceof Integer i |
| switch 表达式 | C# 8.0 | Java 14 |
| 属性模式 | C# 8.0 完整 | Java 21(record 解构) |
| 列表模式 | C# 11.0 | 暂不支持 |
| 关系模式 | C# 9.0 | 暂不支持 |
and / or | C# 9.0 | Java 21 |
| 穷尽性检查 | 警告(不强制) | 强制(sealed 类型 + record) |
Java 示例:
// Java 21
String describe(Object obj) {
return switch (obj) {
case Integer i when i > 0 -> "正整数: " + i;
case String s -> "字符串: " + s;
case null -> "null";
default -> "未知";
};
}
C# 等价代码更简洁:
string describe(object obj) => obj switch {
int i when i > 0 => $"正整数: {i}",
string s => $"字符串: {s}",
null => "null",
_ => "未知"
};
6.2 与 Kotlin 对比
Kotlin 的 when 表达式与 C# switch 表达式非常相似,但 Kotlin 在穷尽性检查上更严格:
// Kotlin
fun describe(x: Any): String = when (x) {
is Int -> "整数: $x"
is String -> "字符串: $x"
else -> "未知"
}
// 对 sealed class 强制穷尽
sealed class Shape
data class Circle(val r: Double) : Shape()
data class Square(val a: Double) : Shape()
fun area(s: Shape) = when (s) {
is Circle -> Math.PI * s.r * s.r
is Square -> s.a * s.a
// 无需 else,因为 sealed 已穷尽
}
| 维度 | C# | Kotlin |
|---|---|---|
| 类型模式 | is int i | is Int + 自动转型 |
| 解构模式 | 位置模式(基于 Deconstruct) | data class 解构 |
| 穷尽性 | 警告 | 强制(sealed class) |
| 范围匹配 | is >= 1 and <= 10 | in 1..10 |
| 列表模式 | C# 11 支持 | 不支持 |
6.3 与 TypeScript 对比
TypeScript 的模式匹配通过判别联合(discriminated union)与 switch 实现:
// TypeScript
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number }
| { kind: "rectangle"; width: number; height: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "square": return s.size ** 2;
case "rectangle": return s.width * s.height;
}
}
TypeScript 在 switch 上做控制流类型收窄,等价于 C# 的类型模式。但 TypeScript 不支持关系模式与列表模式。
6.4 与 Rust match 对比
Rust 的 match 是语言核心,穷尽性强制检查:
// Rust
enum Shape {
Circle { radius: f64 },
Square { side: f64 },
Rectangle { width: f64, height: f64 },
}
fn area(s: Shape) -> f64 {
match s {
Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
Shape::Square { side } => side * side,
Shape::Rectangle { width, height } => width * height,
}
}
| 维度 | C# | Rust |
|---|---|---|
| 穷尽性 | 警告 | 强制(编译错误) |
| 模式种类 | 11 类 | 12+ 类 |
| 绑定 | is int i | x @ 1..=10 |
| 列表模式 | C# 11 支持 | 切片模式 [a, b, ..] |
| 卫语句 | when | if |
| 性能 | 编译器优化 | 编译器优化 |
6.5 与 F# 对比
F# 作为 ML 系语言,模式匹配是其核心特性:
// F#
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
let area s =
match s with
| Circle(r) when r > 0.0 -> Math.PI * r * r
| Circle(_) -> 0.0
| Rectangle(w, h) -> w * h
F# 的 match 默认强制穷尽,且支持活动模式(active patterns):
// F# 活动模式
let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd
let describe n =
match n with
| Even -> "偶数"
| Odd -> "奇数"
C# 13 尚未正式支持活动模式,但社区已通过静态方法 + 模式模拟。
七、常见陷阱与最佳实践
7.1 陷阱:模式顺序错误
// 错误:具体模式被通用模式遮盖
string Classify(int n) => n switch {
>= 0 => "非负", // 这个会先匹配所有正数
> 100 => "大数", // 永远不可达!
_ => "负数"
};
编译器报告 CS8509 警告。正确顺序应为更具体的在前:
string Classify(int n) => n switch {
> 100 => "大数",
>= 0 => "非负",
_ => "负数"
};
7.2 陷阱:when 子句破坏穷尽性
// when 子句使编译器无法证明穷尽性
string Classify(OrderStatus s) => s switch {
OrderStatus.Pending => "待支付",
OrderStatus.Paid when DateTime.Now.Hour < 18 => "已支付(白天)",
OrderStatus.Paid => "已支付(夜间)",
// 其他状态未覆盖,编译器警告 CS8509
_ => "其他"
};
when 子句的运行时条件不在编译期穷尽性分析中,应尽量避免在大范围使用。
7.3 陷阱:null 与属性模式
// 错误:对 null 对象使用属性模式会静默不匹配
if (obj is { Length: > 0 }) { /* obj 为 null 时不进入,不会 NRE */ }
// 但下面的代码会抛 NRE
if (obj is not null && obj.Length > 0) { ... }
属性模式对 null 输入返回不匹配,不抛异常。这是 C# 8+ 的安全设计。但仍需显式处理 null case:
string Describe(string? s) => s switch {
null => "null",
{ Length: 0 } => "空",
{ Length: > 100 } => "过长",
_ => s
};
7.4 陷阱:枚举穷尽性
// 错误:添加新枚举值后未更新 switch
enum Status { Active, Inactive, Deleted }
string Describe(Status s) => s switch {
Status.Active => "活跃",
Status.Inactive => "非活跃",
// 忘记 Deleted
_ => "未知" // 兜底掩盖了穷尽性问题
};
最佳实践:移除 _ 兜底,让编译器强制穷尽性检查:
string Describe(Status s) => s switch {
Status.Active => "活跃",
Status.Inactive => "非活跃",
Status.Deleted => "已删除"
// 新增枚举值时编译器会报 CS8509
};
7.5 陷阱:解构方法签名不一致
// 错误:Deconstruct 签名错误导致位置模式不工作
public class Point {
public double X { get; set; }
public double Y { get; set; }
// 错误签名:应返回 void + out 参数
public (double, double) Deconstruct() => (X, Y);
}
// 正确签名
public void Deconstruct(out double x, out double y) {
x = X;
y = Y;
}
C# 10+ 支持静态 Deconstruct 与扩展 Deconstruct,但实例方法签名必须为 void + out。
7.6 陷阱:列表模式与 IEnumerable<T>
// 错误:列表模式不支持 IEnumerable<T>
IEnumerable<int> seq = GetSequence();
if (seq is [1, 2, 3]) { /* 编译错误 CS8413 */ }
// 正确:列表模式只支持数组与支持索引器的集合
int[] arr = GetArray();
if (arr is [1, 2, 3]) { ... }
// 或先转换为数组
var array = seq.ToArray();
if (array is [1, 2, 3]) { ... }
列表模式要求集合提供 Length/Count 与索引器,普通 IEnumerable<T> 不满足。
7.7 陷阱:is not null 与 != null 的细微差异
string? s = GetString();
// 两种写法语义等价,但 is not null 更现代
if (s is not null) { ... }
if (s != null) { ... }
// 但 is not null 在重载 == 时更安全
public class Weird {
public static bool operator ==(Weird? a, Weird? b) => false; // 故意返回 false
public static bool operator !=(Weird? a, Weird? b) => true;
}
Weird? w = new();
if (w != null) { /* 不进入!因为 != 被重载 */ }
if (w is not null) { /* 进入,is 不受运算符重载影响 */ }
最佳实践:在可空上下文中优先使用 is not null / is null,避免运算符重载干扰。
7.8 最佳实践清单
| 实践 | 说明 |
|---|---|
移除 _ 兜底 | 让编译器强制穷尽性检查 |
| 具体模式在前 | 避免被通用模式遮盖 |
用 is not null 替代 != null | 避免运算符重载干扰 |
| 用 switch 表达式替代 if-else 链 | 当分支数 ≥ 3 时 |
用属性模式替代多重 && | 提高可读性 |
| 用 record 模拟 ADT | 配合模式匹配实现类型安全分支 |
慎用 when 子句 | 会破坏穷尽性检查 |
| 列表模式仅用于数组/索引集合 | 不适用于 IEnumerable<T> |
八、工程实践
8.1 单元测试
// Tests/OrderEventHandlerTests.cs —— xUnit / .NET 8
using FandexPatternMatching.Domain;
using FandexPatternMatching.Handlers;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
public class OrderEventHandlerTests {
private readonly OrderEventHandler _handler = new(NullLogger<OrderEventHandler>.Instance);
[Fact]
public void Handle_LargeOrderCreated_ReturnsLargeOrderMessage() {
var evt = new OrderEvent.Created("ORD-001", 15000m, DateTime.UtcNow);
var result = _handler.Handle(evt);
Assert.Contains("[大额]", result);
}
[Theory]
[InlineData("credit_card", "信用卡支付完成")]
[InlineData("alipay", "alipay 支付完成")]
[InlineData("wechat", "wechat 支付完成")]
public void Handle_PaidEvent_ReturnsMethodSpecificMessage(string method, string expected) {
var evt = new OrderEvent.Paid("ORD-001", method, 100m, DateTime.UtcNow);
var result = _handler.Handle(evt);
Assert.Contains(expected, result);
}
[Fact]
public void Handle_SFPShipped_ReturnsSFMessage() {
var evt = new OrderEvent.Shipped("ORD-001", "SF1234567890", "SF", DateTime.UtcNow);
var result = _handler.Handle(evt);
Assert.Contains("顺丰", result);
}
[Fact]
public void Handle_UnknownEvent_ThrowsInvalidOperationException() {
OrderEvent unknown = null!;
Assert.Throws<NullReferenceException>(() => _handler.Handle(unknown));
}
}
8.2 性能测试(BenchmarkDotNet)
// Benchmarks/PatternMatchingBenchmarks.cs
using BenchmarkDotNet.Attributes;
[MemoryDiagnoser]
public class PatternMatchingBenchmarks {
private object[] _values = null!;
[GlobalSetup]
public void Setup() {
var rand = new Random(42);
_values = new object[1000];
for (int i = 0; i < 1000; i++) {
_values[i] = rand.Next(3) switch {
0 => rand.Next(),
1 => rand.NextDouble(),
2 => rand.Next().ToString()
};
}
}
[Benchmark(Baseline = true)]
public int IfElseChain() {
int sum = 0;
foreach (var v in _values) {
if (v is int i) sum += i;
else if (v is double d) sum += (int)d;
else if (v is string s) sum += s.Length;
}
return sum;
}
[Benchmark]
public int SwitchExpression() {
int sum = 0;
foreach (var v in _values) {
sum += v switch {
int i => i,
double d => (int)d,
string s => s.Length,
_ => 0
};
}
return sum;
}
[Benchmark]
public int PropertyPattern() {
int sum = 0;
foreach (var v in _values) {
sum += v switch {
string { Length: var len } => len,
int i => i,
double d => (int)d,
_ => 0
};
}
return sum;
}
}
典型结果(.NET 8,Release):
| 方法 | 平均时间 | 比值 | 分配 |
|---|---|---|---|
| IfElseChain | 8.21 μs | 1.00 | 0 B |
| SwitchExpression | 8.18 μs | 1.00 | 0 B |
| PropertyPattern | 8.35 μs | 1.02 | 0 B |
三种写法性能几乎等价。编译器将 switch 表达式翻译为等价的 if-else,无额外运行时开销。
8.3 代码风格检查
在 .editorconfig 中配置模式匹配相关规则:
# .editorconfig
[*.cs]
# 优先 switch 表达式而非 switch 语句
dotnet_style_prefer_switch_expression = true:suggestion
# 优先模式匹配而非 as + null 检查
dotnet_style_pattern_matching_over_as_with_null_check = true:suggestion
dotnet_style_pattern_matching_over_is_with_cast_check = true:suggestion
# 优先 `is not null` 而非 `!= null`
dotnet_style_prefer_is_null_check = true:suggestion
# 简化属性模式
dotnet_style_prefer_simplified_interpolation = true:suggestion
8.4 与源生成器结合
源生成器(Source Generator)可基于模式匹配生成代码。例如自动生成枚举的 ToString():
// Generators/EnumToStringGenerator.cs
[Generator]
public class EnumToStringGenerator : IIncrementalGenerator {
public void Initialize(IncrementalGeneratorInitializationContext context) {
var enums = context.SyntaxProvider
.ForAttributeWithMetadataName(
"EnumToStringAttribute",
predicate: (n, _) => n is EnumDeclarationSyntax,
transform: (ctx, _) => (EnumDeclarationSyntax)ctx.TargetNode)
.Where(t => t is not null);
context.RegisterSourceOutput(enums, (sp, decl) => {
var name = decl.Identifier.Text;
var members = decl.Members
.Select(m => $" {name}.{m.Identifier} => nameof({name}.{m.Identifier}),");
var source = $$"""
namespace Generated;
public static class {{name}}Extensions {
public static string ToName(this {{name}} value) => value switch {
{{string.Join("\n", members)}}
_ => value.ToString()
};
}
""";
sp.AddSource($"{name}Extensions.g.cs", source);
});
}
}
九、典型场景与案例研究
9.1 案例一:ASP.NET Core 中间件异常处理
ASP.NET Core 中间件常用模式匹配处理异常:
// ExceptionHandlingMiddleware.cs
public sealed class ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger) {
public async Task InvokeAsync(HttpContext context) {
try {
await next(context);
} catch (Exception ex) {
var (statusCode, message) = ex switch {
// 业务异常
BusinessException { Code: var code } => (
HttpStatusCode.BadRequest,
$"业务错误: {code}"
),
// 验证异常
ValidationException ve => (
HttpStatusCode.UnprocessableEntity,
ve.Message
),
// 未授权
UnauthorizedAccessException => (
HttpStatusCode.Unauthorized,
"未授权"
),
// 资源不存在
NotFoundException { ResourceName: var name } => (
HttpStatusCode.NotFound,
$"{name} 不存在"
),
// 5xx 服务端错误
_ when ex is IOException or SqlException => (
HttpStatusCode.InternalServerError,
"服务暂时不可用"
),
// 兜底
_ => (
HttpStatusCode.InternalServerError,
"未知错误"
)
};
logger.LogError(ex, "异常: {Message}", message);
context.Response.StatusCode = (int)statusCode;
await context.Response.WriteAsJsonAsync(new { error = message });
}
}
}
9.2 案例二:EF Core 中的查询条件构建
利用模式匹配动态构建 LINQ 表达式:
// QueryBuilder.cs
public static IQueryable<Order> ApplyFilter(IQueryable<Order> query, OrderFilter filter) {
// 用模式匹配拆解筛选条件
return filter switch {
{ Status: not null, MinAmount: not null, MaxAmount: not null } =>
query.Where(o => o.Status == filter.Status
&& o.Amount >= filter.MinAmount
&& o.Amount <= filter.MaxAmount),
{ Status: not null, MinAmount: not null } =>
query.Where(o => o.Status == filter.Status && o.Amount >= filter.MinAmount),
{ Status: not null } =>
query.Where(o => o.Status == filter.Status),
{ MinAmount: not null, MaxAmount: not null } =>
query.Where(o => o.Amount >= filter.MinAmount && o.Amount <= filter.MaxAmount),
{ CustomerId: not null } =>
query.Where(o => o.Customer.Id == filter.CustomerId),
// 空筛选条件
{ } when filter == OrderFilter.Empty => query,
_ => query
};
}
public record OrderFilter(
OrderStatus? Status,
decimal? MinAmount,
decimal? MaxAmount,
string? CustomerId) {
public static readonly OrderFilter Empty = new(null, null, null, null);
}
9.3 案例三:领域事件版本化
处理领域事件的版本演进:
// EventVersioningHandler.cs
public string HandleVersionedEvent(object rawEvent) => rawEvent switch {
// v3 事件(最新)
OrderEventV3 { OrderId: var id, Amount: var amt, Currency: var c } =>
$"[v3] 订单 {id} 金额 {amt} {c}",
// v2 事件(兼容)
OrderEventV2 { OrderId: var id, Amount: var amt } =>
$"[v2] 订单 {id} 金额 {amt} USD(默认币种)",
// v1 事件(兼容)
OrderEventV1 { Id: var id } =>
$"[v1] 订单 {id} 金额未知(旧版)",
// JSON 反序列化的 JObject
JObject j when j["version"]?.Value<int>() == 3 =>
Handle(JsonConvert.DeserializeObject<OrderEventV3>(j.ToString())!),
// 未知版本
_ => throw new InvalidOperationException($"未知事件: {rawEvent.GetType().Name}")
};
9.4 案例四:JSON 解析
使用列表模式解析 JSON 路径:
// JsonPathParser.cs
public static object? GetByPath(JsonElement root, string path) {
var segments = path.Split('/');
var current = root;
foreach (var seg in segments) {
current = current switch {
{ ValueKind: JsonValueKind.Object } o when o.TryGetProperty(seg, out var v) => v,
{ ValueKind: JsonValueKind.Array } a when int.TryParse(seg, out var idx) && idx < a.GetArrayLength() =>
a[idx],
_ => throw new KeyNotFoundException($"路径 {seg} 不存在")
};
}
return current.ValueKind switch {
JsonValueKind.String => current.GetString(),
JsonValueKind.Number => current.GetDouble(),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
_ => current
};
}
9.5 案例五:规则引擎
基于模式匹配构建可扩展规则引擎:
// Rules/RuleEngine.cs
public sealed class RuleEngine {
private readonly List<Rule> _rules = new();
public RuleEngine AddRule(Rule rule) {
_rules.Add(rule);
return this;
}
public RuleResult Evaluate(Order order) =>
_rules
.Select(r => r.TryMatch(order))
.FirstOrDefault(r => r.IsMatch) ?? RuleResult.NoMatch;
}
public abstract record Rule {
public abstract RuleResult TryMatch(Order order);
public record VipDiscount(decimal Rate) : Rule {
public override RuleResult TryMatch(Order order) => order switch {
{ Customer.VipLevel: >= 5, Amount: > 1000 } =>
new RuleResult(true, Rate, $"VIP 5+ 大额 {Rate:P0}"),
_ => RuleResult.NoMatch
};
}
public record CityDiscount(string City, decimal Rate) : Rule {
public override RuleResult TryMatch(Order order) => order switch {
{ Customer.City: var c, Amount: > 500 } when c == City =>
new RuleResult(true, Rate, $"{City} 大额 {Rate:P0}"),
_ => RuleResult.NoMatch
};
}
public record CompositeDiscount(Rule First, Rule Second) : Rule {
public override RuleResult TryMatch(Order order) => (First.TryMatch(order), Second.TryMatch(order)) switch {
({ IsMatch: true } r1, { IsMatch: true } r2) =>
new RuleResult(true, r1.Rate + r2.Rate, $"{r1.Reason} + {r2.Reason}"),
({ IsMatch: true } r1, _) => r1,
(_, { IsMatch: true } r2) => r2,
_ => RuleResult.NoMatch
};
}
}
public record RuleResult(bool IsMatch, decimal Rate, string Reason) {
public static readonly RuleResult NoMatch = new(false, 0m, "无匹配规则");
}
十、练习题与参考答案
10.1 基础题
题目 1:用 switch 表达式实现一个将 HTTP 状态码转换为描述的函数。
参考答案:
string StatusDescription(int code) => code switch {
200 => "OK",
201 => "Created",
204 => "No Content",
301 => "Moved Permanently",
302 => "Found",
400 => "Bad Request",
401 => "Unauthorized",
403 => "Forbidden",
404 => "Not Found",
>= 500 and < 600 => "Server Error",
_ => "Unknown"
};
10.2 进阶题
题目 2:用属性模式与逻辑模式实现用户权限校验。
参考答案:
public sealed class PermissionChecker {
public bool CanAccess(User user, Resource resource) => (user, resource) switch {
// 管理员可访问一切
({ Role: UserRole.Admin }, _) => true,
// 拥有者可访问自己的资源
({ Id: var uid }, { OwnerId: var oid }) when uid == oid => true,
// 编辑者可访问公开资源
({ Role: UserRole.Editor }, { Visibility: ResourceVisibility.Public }) => true,
// 已激活的普通用户可读公开资源
({ Status: UserStatus.Active }, { Visibility: ResourceVisibility.Public, Type: ResourceType.Readable }) => true,
// 其他情况
_ => false
};
}
10.3 应用题
题目 3:实现一个基于模式匹配的状态机,处理订单状态转换。
参考答案:
public sealed class OrderStateMachine {
public OrderStatus? Next(OrderEvent evt, OrderStatus current) => (evt, current) switch {
// 待支付 -> 已支付
(OrderEvent.Paid, OrderStatus.Pending) => OrderStatus.Paid,
// 已支付 -> 已发货
(OrderEvent.Shipped, OrderStatus.Paid) => OrderStatus.Shipped,
// 已发货 -> 已送达
({ } e, OrderStatus.Shipped) when e is OrderEvent.Refunded or OrderEvent.Cancelled =>
throw new InvalidOperationException("已发货订单不可直接取消/退款"),
// 任意状态 -> 已取消(除已发货/已送达)
(OrderEvent.Cancelled, not OrderStatus.Shipped and not OrderStatus.Delivered) =>
OrderStatus.Cancelled,
// 已支付 -> 已退款
(OrderEvent.Refunded, OrderStatus.Paid) => OrderStatus.Refunded,
// 非法转换
_ => null
};
}
10.4 高阶题
题目 4:用列表模式实现一个简单的 JSON 数组解析器。
参考答案:
public static int SumJsonArray(string json) {
// 假设 json 形如 "[1, 2, 3, 4]"
var trimmed = json.Trim('[', ']', ' ');
var parts = trimmed.Split(',').Select(p => p.Trim()).ToArray();
return parts switch {
[] => 0,
[var single] when int.TryParse(single, out var v) => v,
[var first, .. var rest] when int.TryParse(first, out var v) =>
v + SumJsonArray($"[{string.Join(", ", rest)}]"),
_ => throw new FormatException($"无效 JSON: {json}")
};
}
10.5 思考题
题目 5:为什么 C# 选择警告而非错误来处理 switch 表达式的穷尽性?这种设计的优缺点是什么?
参考答案:
优点:
- 向后兼容:新增枚举值不会破坏现有代码编译;
- 灵活性:允许通过
_兜底显式表达”其他情况”; - 渐进式迁移:从 C# 7 升级到 C# 8+ 不会引入大量编译错误。
缺点:
- 容易遗漏新枚举值,引入运行时 bug;
_兜底掩盖了穷尽性问题;- 与 F#、Rust 等强制穷尽的语言相比,类型安全较弱。
折中方案:
- 在新代码中移除
_兜底,让编译器强制穷尽; - 在
.editorconfig中将 CS8509 升级为错误:dotnet_diagnostic.CS8509.severity = error
10.6 设计题
题目 6:设计一个支持活动模式(active patterns)的扩展方法系统,用于识别偶数、质数、斐波那契数。
参考答案:
public static class NumberPatterns {
// 通过静态方法模拟活动模式
public static bool IsEven(this int n) => n % 2 == 0;
public static bool IsPrime(this int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++)
if (n % i == 0) return false;
return true;
}
public static bool IsFibonacci(this int n) {
bool IsPerfectSquare(int x) {
var s = (int)Math.Sqrt(x);
return s * s == x;
}
return IsPerfectSquare(5 * n * n + 4) || IsPerfectSquare(5 * n * n - 4);
}
}
// 使用:结合 when 子句
string Classify(int n) => n switch {
_ when n.IsFibonacci() => $"斐波那契数: {n}",
_ when n.IsPrime() => $"质数: {n}",
_ when n.IsEven() => $"偶数: {n}",
_ => $"普通数: {n}"
};
10.7 综合题
题目 7:实现一个基于模式匹配的 JSON Schema 校验器(简化版)。
参考答案:
public sealed class JsonSchemaValidator {
public bool Validate(JsonElement value, JsonSchema schema) => (value, schema) switch {
// null 校验
({ ValueKind: JsonValueKind.Null }, JsonSchema.Null) => true,
({ ValueKind: not JsonValueKind.Null }, JsonSchema.Null) => false,
// 字符串校验
({ ValueKind: JsonValueKind.String } s, JsonSchema.String { MinLength: var min, MaxLength: var max }) => {
var len = s.GetString()?.Length ?? 0;
return len >= min && len <= max;
},
// 数字校验
({ ValueKind: JsonValueKind.Number } n, JsonSchema.Number { Min: var min, Max: var max }) => {
var v = n.GetDouble();
return v >= min && v <= max;
},
// 布尔校验
({ ValueKind: JsonValueKind.True or JsonValueKind.False }, JsonSchema.Boolean) => true,
// 数组校验
({ ValueKind: JsonValueKind.Array } arr, JsonSchema.Array { Items: var itemSchema, MinItems: var min, MaxItems: var max }) => {
var count = arr.GetArrayLength();
if (count < min || (max > 0 && count > max)) return false;
foreach (var item in arr.EnumerateArray())
if (!Validate(item, itemSchema)) return false;
return true;
},
// 默认
_ => false
};
}
public abstract record JsonSchema {
public record Null : JsonSchema;
public record String(int MinLength = 0, int MaxLength = int.MaxValue) : JsonSchema;
public record Number(double Min = double.MinValue, double Max = double.MaxValue) : JsonSchema;
public record Boolean : JsonSchema;
public record Array(JsonSchema Items, int MinItems = 0, int MaxItems = 0) : JsonSchema;
}
10.8 算法题
题目 8:用模式匹配实现归并排序的合并步骤。
参考答案:
public static int[] Merge(int[] a, int[] b) => (a, b) switch {
([], var rest) => rest,
(var rest, []) => rest,
[var ha, .. var ta], [var hb, .. var tb] when ha <= hb =>
new[] { ha }.Concat(Merge(ta.ToArray(), b)).ToArray(),
[var ha, .. var ta], [var hb, .. var tb] =>
new[] { hb }.Concat(Merge(a, tb.ToArray())).ToArray()
};
10.9 错误诊断题
题目 9:以下代码有什么问题?如何修复?
string Classify(object obj) => obj switch {
int => "整数",
string => "字符串",
_ => "未知"
};
参考答案:
问题:类型模式需要绑定变量(C# 9+ 后可省略变量名,但 C# 7-8 必须有)。
修复(C# 9+):
string Classify(object obj) => obj switch {
int => "整数",
string => "字符串",
_ => "未知"
};
修复(C# 7-8):
string Classify(object obj) => obj switch {
int _ => "整数",
string _ => "字符串",
_ => "未知"
};
10.10 实战题
题目 10:用模式匹配实现一个简单的银行账户交易处理器,支持存款、取款、转账,并处理各种异常情况。
参考答案:
public abstract record Transaction {
public record Deposit(string AccountId, decimal Amount, DateTime At) : Transaction;
public record Withdraw(string AccountId, decimal Amount, DateTime At) : Transaction;
public record Transfer(string From, string To, decimal Amount, DateTime At) : Transaction;
}
public sealed class TransactionProcessor {
public TransactionResult Process(Transaction tx, Dictionary<string, Account> accounts) => tx switch {
// 存款
Transaction.Deposit { AccountId: var id, Amount: > 0 } when accounts.ContainsKey(id) => {
accounts[id].Balance += tx.Amount;
return new TransactionResult(true, "存款成功");
},
// 取款 - 余额不足
Transaction.Withdraw { AccountId: var id, Amount: var amt } when amt > accounts[id].Balance =>
new TransactionResult(false, "余额不足"),
// 取款 - 成功
Transaction.Withdraw { AccountId: var id, Amount: > 0 } when accounts.ContainsKey(id) => {
accounts[id].Balance -= tx.Amount;
return new TransactionResult(true, "取款成功");
},
// 转账 - 同一账户
Transaction.Transfer { From: var f, To: var t } when f == t =>
new TransactionResult(false, "不能转账给同一账户"),
// 转账 - 余额不足
Transaction.Transfer { From: var f, Amount: var amt } when amt > accounts[f].Balance =>
new TransactionResult(false, "余额不足"),
// 转账 - 成功
Transaction.Transfer { From: var f, To: var t, Amount: > 0 } => {
accounts[f].Balance -= tx.Amount;
accounts[t].Balance += tx.Amount;
return new TransactionResult(true, "转账成功");
},
// 金额非法
_ when tx is Transaction.Deposit { Amount: <= 0 }
or Transaction.Withdraw { Amount: <= 0 }
or Transaction.Transfer { Amount: <= 0 } =>
new TransactionResult(false, "金额必须为正"),
// 账户不存在
_ => new TransactionResult(false, "账户不存在")
};
}
public class Account { public decimal Balance { get; set; } }
public record TransactionResult(bool Success, string Message);
十一、参考文献
本节按 ACM Reference Format 列出本文主要参考资料。
[1] Ecma International. 2023. ECMA-334: The C# Language Specification (6th ed.). ECMA, Geneva. Retrieved July 21, 2026 from https://www.ecma-international.org/wp-content/uploads/ECMA-334_6th_edition_december_2022.pdf
[2] Anders Hejlsberg, Mads Torgersen, Scott Wiltamuth, and Peter Golde. 2010. The C# Programming Language (4th ed.). Addison-Wesley Professional, Boston, MA. DOI: https://doi.org/10.5555/1861685
[3] Mads Torgersen. 2017. Pattern Matching in C# 7.0. .NET Blog. Retrieved July 21, 2026 from https://devblogs.microsoft.com/dotnet/pattern-matching-in-csharp-7-0/
[4] Mads Torgersen. 2018. Pattern Matching in C# 8.0. .NET Blog. Retrieved July 21, 2026 from https://devblogs.microsoft.com/dotnet/take-csharp-8-0-for-a-spin/
[5] Mads Torgersen. 2020. Pattern Matching in C# 9.0. .NET Blog. Retrieved July 21, 2026 from https://devblogs.microsoft.com/dotnet/welcome-to-csharp-9/
[6] Mads Torgersen. 2022. Pattern Matching in C# 11. .NET Blog. Retrieved July 21, 2026 from https://devblogs.microsoft.com/dotnet/welcome-to-csharp-11/
[7] Microsoft. 2024. Patterns (C# reference). Microsoft Learn. Retrieved July 21, 2026 from https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns
[8] Microsoft. 2024. Switch expressions (C# reference). Microsoft Learn. Retrieved July 21, 2026 from https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/switch-expression
[9] Don Syme, Gregory Neverov, and James Margetson. 2007. Extensible Pattern Matching via a Lightweight Language Extension. In Proceedings of the 12th ACM SIGPLAN International Conference on Functional Programming (ICFP ‘07). ACM, Freiburg, Germany, 29-40. DOI: https://doi.org/10.1145/1291220.1291159
[10] Lennart C. L. Kats, Eelco Visser, and Guido Wachsmuth. 2010. Pure and Declarative Syntax Definition: The Disambiguation Problem Solved. In Proceedings of the 19th ACM SIGPLAN International Conference on Functional Programming (ICFP ‘10). ACM, Baltimore, MD, 87-99. DOI: https://doi.org/10.1145/1863543.1863560
[11] Philip Wadler. 1987. Views: A Way for Pattern Matching to Cohabit with Data Abstraction. In Proceedings of the 14th ACM SIGACT-SIGPLAN Symposium on Principles of Programming Languages (POPL ‘87). ACM, Munich, Germany, 307-313. DOI: https://doi.org/10.1145/41625.41653
[12] Malcolm Wallace and Colin Runciman. 1998. Liberating Type-Specific Optimizations. In Proceedings of the 3rd ACM SIGPLAN Workshop on Types in Compilation (TIC ‘98). ACM, Kyoto, Japan. Retrieved July 21, 2026 from https://dl.acm.org/doi/10.5555/970706
[13] Bertrand Meyer. 2009. Touch of Class: Learning to Program Well with Objects and Contracts. Springer, Berlin. DOI: https://doi.org/10.1007/978-3-540-92145-5
[14] Jon Skeet. 2019. C# in Depth (4th ed.). Manning Publications, Shelter Island, NY. Retrieved July 21, 2026 from https://csharpindepth.com/
[15] Mark Michaelis. 2022. Essential C# 11.0 (7th ed.). Addison-Wesley Professional, Boston, MA. DOI: https://doi.org/10.5555/3593866
[16] Andrew Kennedy and Don Syme. 2001. Design and Implementation of Generics for the .NET Common Language Runtime. In Proceedings of the 22nd ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL ‘01). ACM, London, UK, 1-12. DOI: https://doi.org/10.1145/360204.372037
[17] ECMA International. 2012. ECMA-335: Common Language Infrastructure (CLI) (6th ed.). ECMA, Geneva. Retrieved July 21, 2026 from https://www.ecma-international.org/wp-content/uploads/ECMA-335_6th_edition_december_2012.pdf
[18] Microsoft. 2024. C# 13.0 specification: Patterns. Microsoft Learn. Retrieved July 21, 2026 from https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-13.0
十二、延伸阅读
12.1 官方文档
- Patterns - C# reference | Microsoft Learn
- Switch expressions - C# reference | Microsoft Learn
- Pattern matching overview - C# guide | Microsoft Learn
- C# language versioning - Microsoft Learn
12.2 经典书籍
- Jon Skeet, C# in Depth (4th Edition) - 第 14 章深入讲解模式匹配
- Mark Michaelis, Essential C# 11.0 - 第 12 章模式匹配专题
- Joseph Albahari, C# 12 in a Nutshell - 模式匹配速查
- Andrew Troelsen, Pro C# 10 with .NET 6 - 第 4 章类型系统与模式匹配
12.3 设计文档与 RFC
- C# 7 Pattern Matching Proposal
- C# 8 Recursive Pattern Matching Proposal
- C# 9 Patterns: and/or/not/relational
- C# 11 List Patterns Proposal
12.4 相关论文
- Wadler, P. (1987). Views: A Way for Pattern Matching to Cohabit with Data Abstraction. POPL ‘87.
- Syme, D., Neverov, G., Margetson, J. (2007). Extensible Pattern Matching via a Lightweight Language Extension. ICFP ‘07.
- Emir, B., Odersky, M., Williams, J. (2007). Matching Objects with Patterns. ECOOP ‘07.
12.5 相关 F# 文档
- Pattern Matching - F# Guide | Microsoft Learn
- Active Patterns - F# Guide | Microsoft Learn
- Syme, D. The F# Language Specification.
12.6 相关语言对比
- Rust Pattern Matching
- Scala Pattern Matching
- Kotlin when expression
- Java Pattern Matching for switch
12.7 开源项目参考
- dotnet/csharplang GitHub - C# 语言设计官方仓库
- dotnet/roslyn GitHub - C# 编译器源码
- LanguageExt - C# 函数式编程库,含模式匹配扩展
- OneOf - C# 判别联合库
12.8 进阶主题
- 活动模式(Active Patterns):F# 已支持,C# 社区正在讨论
- 模式绑定:将模式匹配结果直接绑定到变量
- 视图模式(View Patterns):Haskell 风格的自定义模式
- 结构性模式:基于对象结构而非类型的匹配
- 模式匹配与表达式树:将模式翻译为可执行表达式树
- 模式匹配与源生成器:编译期生成模式匹配代码
- 模式匹配与异构数据:处理 JSON、XML 等异构数据源
12.9 性能与底层
- Pattern Matching in .NET 8: Performance Improvements | .NET Blog
- Roslyn Pattern Matching Compilation | GitHub
- C# Pattern Matching IL Generation | GitHub
12.10 教学视频
- C# Pattern Matching Tutorial - Microsoft Learn TV
- Pattern Matching Deep Dive - NDC Conference 2023
- Functional C# with Pattern Matching - F# for Fun and Profit
- C# 11 List Patterns in Action - Nick Chapsas YouTube
总结:模式匹配是 C# 自 7.0 以来最重要的语言特性之一,它将条件逻辑从命令式的 if-else 提升为声明式的数据形状描述。从类型模式到列表模式,C# 逐步吸收了 ML 系语言的核心思想,同时在保持向后兼容与性能的前提下,提供了接近 F#、Rust 的表达力。掌握模式匹配不仅能让代码更简洁安全,更能改变设计思路——从”如何做”转向”是什么”。
阅读建议:
- 初学者:先掌握类型模式与 switch 表达式,重构
if-else链; - 中级开发者:学习属性模式、位置模式、逻辑模式,应用于领域建模;
- 高级开发者:研究列表模式、递归模式、活动模式,构建 DSL 与规则引擎;
- 架构师:用模式匹配设计穷尽性强的领域模型,结合源生成器实现编译期检查。
后续推荐阅读:
- 《记录类型与不可变性》:与模式匹配配合的最佳搭档
- 《源生成器》:编译期代码生成与模式匹配结合
- 《表达式树》:理解模式匹配的编译期翻译
- 《F# 入门》:体验完整穷尽性检查与活动模式