C# 模式匹配(Pattern Matching)
官方文档:
- 模式匹配概述 - C# | Microsoft Learn、
- 使用模式匹配避免后跟强制转换的"is"检查
- 模式匹配完整参考
- 模式匹配实践教程
标签:record、Deconstruct
PS:Deconstruct可查看我的另一篇👉C# Deconstruct | 简化元组与对象的数据提取-CSDN博客
目录
- 1. 概述
- 1.1 支持以下功能
- 1.2 应用场景
- 1.3 优点
- 2. 传统方式 vs 模式匹配
- 2.1 传统方式(C# 6.0 及之前)
- 2.2 模式匹配方式(C# 7.0+)
- 3. C# 中的模式匹配类型
- 3.1 类型模式(Type Pattern)
- 3.2 声明模式(Declaration Pattern)
- 3.3 var 模式
- 3.4 常量模式 (Constant Pattern)
- 3.5 关系模式(Relational Pattern)
- 3.5 逻辑模式(Logical Pattern)
- 3.6 属性模式(Property Pattern)
- 3.7 位置模式(Positional Pattern)- 与 record 配合使用
- 3.8 列表模式 **(List Pattern)**(C# 11+)
- 3. 实际应用示例
- 4. 优势
- 5. 最佳实践
- 6. 总结
1. 概述
模式匹配是现代编程语言中的一种强大表达力丰富的条件分支方式,它允许检查值的特征(如类型、属性、值等)来简化条件逻辑,并根据检查结果执行相应的代码。
随着版本更新,C# 的模式匹配能力不断增强。
1.1 支持以下功能
- 解构提取 – 从复杂结构(如对象、元组等)中提取出内部元素
- 类型检查 – 判断值的类型是否符合预期
- 条件逻辑 – 将条件判断与变量绑定结合,增强代码表达力
- 声明式条件处理 – 以更清晰、结构化的方式编写多分支条件逻辑
1.2 应用场景
is
表达式:直接条件判断。switch
表达式:返回值的模式匹配。switch
语句:多分支条件判断。
1.3 优点
模式匹配提高了代码的:
- 可读性:更简洁地表达复杂条件
- 安全性:编译器验证完整性,减少遗漏情况
- 表达力:支持丰富的模式组合和嵌套
2. 传统方式 vs 模式匹配
2.1 传统方式(C# 6.0 及之前)
// 需要多次类型检查和转换
if (shape is Circle)
{Circle circle = (Circle)shape;if (circle.Radius > 5){Console.WriteLine($"Large circle: {circle.Radius}");}
}
else if (shape is Rectangle)
{Rectangle rect = (Rectangle)shape;Console.WriteLine($"Rectangle: {rect.Width}x{rect.Height}");
}
2.2 模式匹配方式(C# 7.0+)
// 单一行内完成类型检查、条件判断和转换
if (shape is Circle { Radius: > 5 } circle)
{Console.WriteLine($"Large circle: {circle.Radius}");
}
else if (shape is Rectangle rect)
{Console.WriteLine($"Rectangle: {rect.Width}x{rect.Height}");
}
3. C# 中的模式匹配类型
3.1 类型模式(Type Pattern)
主要用于检查表达式的运行时类型,通常与 switch
表达式配合使用来实现基于类型的分支处理。
-
检查表达式是否匹配指定类型(不声明变量)。
if (obj is string) { Console.WriteLine("这是一个字符串"); }
-
与
switch
表达式配合使用var area = shape switch {Circle c => Math.PI * c.Radius * c.Radius,Rectangle r => r.Width * r.Height,_ => 0 // 处理 null 和其他值,也叫做弃元模式 };
3.2 声明模式(Declaration Pattern)
同时进行类型检查和变量声明。
如果表达式能与指定类型匹配,则条件为真,同时会创建一个新的类型化变量(此例中的 circle
)来访问该实例的成员,从而避免了显式类型转换。
if (shape is Circle circle)
{Console.WriteLine($"Circle with radius {circle.Radius}");
}
3.3 var 模式
匹配任何表达式(包括 null
)并将结果分配给变量。
static bool IsAcceptable(int id, int absLimit) =>SimulateDataFetch(id) is var results && results.Min() >= -absLimit && results.Max() <= absLimit;// 在 when 子句中使用
static Point Transform(Point point) => point switch
{var (x, y) when x < y => new Point(-x, y),var (x, y) when x > y => new Point(x, -y),var (x, y) => new Point(x, y),
};
3.4 常量模式 (Constant Pattern)
检查表达式是否等于指定常量(包括 null
、枚举、字面量等)。
if (value is null) return;
if (value is 42) Console.WriteLine("答案是42");
if (value is "hello") Console.WriteLine("打招呼");
枚举值匹配
public State PerformOperation(Operation command) =>command switch{Operation.SystemTest => RunDiagnostics(),Operation.Start => StartSystem(),Operation.Stop => StopSystem(),Operation.Reset => ResetToReady(),_ => throw new ArgumentException("Invalid enum value for command", nameof(command)),};
字符串值匹配
public State PerformOperation(string command) =>command switch{"SystemTest" => RunDiagnostics(),"Start" => StartSystem(),"Stop" => StopSystem(),"Reset" => ResetToReady(),_ => throw new ArgumentException("Invalid string value for command", nameof(command)),};
3.5 关系模式(Relational Pattern)
此模式允许在模式匹配中使用关系运算符(<
, >
, <=
, >=
)来将属性值与常量进行比较。它使得基于数值范围的匹配变得非常直观和清晰,完美替代了多个 if-else if
语句链。
// 使用关系运算符进行比较
string sizeCategory = shape switch
{Circle { Radius: >= 10 } => "Extra large",Circle { Radius: >= 5 } => "Large",Circle { Radius: >= 1 } => "Medium",Circle { Radius: > 0 } => "Small",_ => "Invalid"
};
3.5 逻辑模式(Logical Pattern)
逻辑模式允许您使用逻辑运算符 and
、or
和 not
将多个模式组合起来,形成一个更复杂的复合条件。
模式组合器优先级(从高到低):not
> and
> or
// 使用逻辑运算符组合条件
string category = shape switch
{Circle { Radius: > 0 and <= 5 } => "Small to medium circle",Rectangle { Width: > 0, Height: > 0 } and not { Width: var w, Height: var h } when w == h => "Non-square rectangle",Rectangle { Width: var w, Height: var h } when w == h => "Square",_ => "Other"
};//Null 检查
string? message = ReadMessageOrDefault();if (message is not null)
{Console.WriteLine(message);
}int? maybe = 12;if (maybe is int number)
{Console.WriteLine($"The nullable int 'maybe' has the value {number}");
}
else
{Console.WriteLine("The nullable int 'maybe' doesn't hold a value");
}
3.6 属性模式(Property Pattern)
属性模式允许直接对对象的属性值进行匹配,而无需先检查类型再访问属性,支持递归匹配。
if (person is { Address.City: "Beijing" })
{Console.WriteLine("北京人");
}
static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };// 结合类型检查和变量声明
static string TakeFive(object input) => input switch
{string { Length: >= 5 } s => s[..5],string s => s,ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),ICollection<char> symbols => new string(symbols.ToArray()),null => throw new ArgumentNullException(nameof(input)),_ => throw new ArgumentException("Not supported input type."),
};// 扩展属性模式(C# 10+)
static bool IsInDomain(WeightedPoint point) => point is { X: >= 0, Y: >= 0, Weight: >= 0.0 };
3.7 位置模式(Positional Pattern)- 与 record 配合使用
此模式用于对实现了 Deconstruct 方法的类型(如元组(Tuple)、记录(record)类型)进行解构,并根据其解构后的元素值进行匹配。
_
代表丢弃符,表示忽略该位置的值。它是处理元组和记录等复合数据的强大工具。
public readonly struct Point
{public int X { get; }public int Y { get; }public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}static string Classify(Point point) => point switch
{(0, 0) => "Origin",(1, 0) => "positive X basis end",(0, 1) => "positive Y basis end",_ => "Just a point",
};// 元组模式匹配
static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate) => (groupSize, visitDate.DayOfWeek) switch
{(<= 0, _) => throw new ArgumentException("Group size must be positive."),(_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,(>= 5 and < 10, DayOfWeek.Monday) => 20.0m,(>= 10, DayOfWeek.Monday) => 30.0m,(>= 5 and < 10, _) => 12.0m,(>= 10, _) => 15.0m,_ => 0.0m,
};
3.8 列表模式 (List Pattern)(C# 11+)
处理不规则数据结构,匹配数组或列表中的元素序列,支持切片模式 ..
。
int[] numbers = { 1, 2, 3 };// 基本匹配
Console.WriteLine(numbers is [1, 2, 3]); // True// 结合关系模式和弃元模式
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]); // True// 使用切片模式
Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]); // True
Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]); // True// 捕获切片结果
void MatchMessage(string message)
{var result = message is ['a' or 'A', .. var s, 'a' or 'A']? $"Message {message} matches; inner part is {s}.": $"Message {message} doesn't match.";Console.WriteLine(result);
}
MatchMessage("aBBA"); // output: Message aBBA matches; inner part is BB.
decimal balance = 0m;
foreach (string[] transaction in ReadRecords())
{balance += transaction switch{[_, "DEPOSIT", _, var amount] => decimal.Parse(amount),[_, "WITHDRAWAL", .., var amount] => -decimal.Parse(amount),[_, "INTEREST", var amount] => decimal.Parse(amount),[_, "FEE", var fee] => -decimal.Parse(fee),_ => throw new InvalidOperationException($"Record {string.Join(", ", transaction)} is not in the expected format!"),};Console.WriteLine($"Record: {string.Join(", ", transaction)}, New balance: {balance:C}");
}
3. 实际应用示例
public record Order(string Id, decimal Amount, string Status, DateTime OrderDate);public decimal CalculateDiscount(Order order) => order switch
{// 高金额且是新客户 - 大折扣{ Amount: > 1000, Status: "New" } => 0.15m,// 节假日订单 - 中等折扣{ OrderDate: { Month: 12, Day: >= 20 and <= 31 } } => 0.10m,// 普通订单 - 小折扣{ Amount: > 500 } => 0.05m,// 默认无折扣_ => 0m
};// 使用示例
var order = new Order("123", 1200m, "New", new DateTime(2023, 12, 25));
decimal discount = CalculateDiscount(order); // 返回 0.15 (15% 折扣)
4. 优势
- 更简洁的代码:减少样板代码
- 更安全:编译器可以帮助检查穷尽性
- 更表达力强:清晰表达复杂的条件逻辑
- 更好的可读性:意图更加明确
- 与 record 类型完美配合:充分利用 record 的解构功能
5. 最佳实践
- 使用括号明确优先级:当组合复杂模式时,使用括号提高可读性和确保正确优先级
- 穷尽性检查:确保
switch
表达式处理所有可能情况,避免运行时异常 - 性能考量:模式匹配通常比传统 if-else 链更高效,编译器会优化模式匹配逻辑
- 可读性:对于复杂匹配逻辑,考虑将模式分解为多个简单模式或使用辅助方法
6. 总结
模式匹配通过将类型检查、属性检查和条件逻辑结合在一起,它提供了比传统 if-else
和 switch
语句更强大、更简洁的表达能力。
建议根据需求选择合适的模式,并注意兼容的 C# 版本(如列表模式需 C# 11+)。