类型检查和转换:当你需要检查对象是否为特定类型,并且希望在同一时间内将其转换为那个类型时,模式匹配提供了一种更简洁的方式来完成这一任务,避免了使用传统的as和is操作符后还需要进行额外的null检查。
复杂条件逻辑:在处理复杂的条件逻辑时,特别是涉及到多个条件和类型的情况下,使用模式匹配可以使代码更加清晰易读。通过模式匹配,可以将复杂的if-else链或switch语句简化,使逻辑更直观。
解构复合类型:当你需要从复合类型(如元组、自定义类等)中提取值时,模式匹配允许你直接在条件检查中进行解构,这样可以避免编写额外的解构代码,使得代码更加简洁。
范围检查:对于需要进行范围检查的场景,如检查一个数是否落在某个区间内,使用C# 9.0引入的关系模式可以极大简化代码,使得范围检查逻辑一目了然。
逻辑组合:在需要对多个条件进行逻辑组合的情况下,如需要检查一个值是否满足多个条件之一或全部条件,使用逻辑模式可以直接在模式匹配表达式中使用and、or和not运算符,避免了复杂的逻辑嵌套。
数据验证:模式匹配可以用于数据验证场景,特别是当验证逻辑涉及到类型检查、值范围检查或特定属性值检查时。通过模式匹配,可以在单个表达式中完成所有这些检查,使得验证逻辑更加紧凑和易于维护。
多态行为:在处理需要根据对象类型执行不同操作的多态行为时,模式匹配提供了一种更灵活的方式来替代传统的虚方法或接口实现。这使得在不修改原有类层次结构的情况下,能够更容易地扩展或修改行为。
替代访问者模式:在实现访问者设计模式时,模式匹配可以作为一种更简洁的替代方案,特别是在处理复杂的对象结构时。通过模式匹配,可以直接在一个地方处理所有类型的情况,而不需要为每种类型创建单独的访问者方法。
模式匹配的这些用途展示了它在简化代码、提高可读性和灵活处理不同类型和条件的强大能力。随着C#语言的发展,模式匹配的功能和应用场景将会进一步扩展和深化。
下面我们看下一些经典的模式匹配编码风格:
is断言 变量str已被安全地转换为string类型
object obj = "Hello, World!";
if (obj is string str) {
Console.WriteLine(str);
}
is对可空类型的断言
public record Person(int Id, string? Name, bool? IsActived);
var person = new Person(1, "vipwan", null);
if (person?.IsActived is true)
{
Console.WriteLine($"Id {person.Id} 已激活");
}
switch 允许使用多种模式,包括类型模式、常量模式和var模式 ,无需我们提前做转换以节省编码量
var obj = 0;
switch (obj)
{
case 0:
Console.WriteLine("Zero");
break;
case var value when value > 100:
Console.WriteLine($"Value: {value}>100");
break;
default:
Console.WriteLine($"Value: {obj}");
break;
}
switch 中使用弃元_代替变量
public static string CronEveryNHours(this int n) => n switch
{
(>= 1 and < 24) => $"0 0/{n} * * *",
_ => throw new ArgumentException("n must be between 1 and 24", nameof(n))
};
switch 中多个条件一起判断
public record Order(int Items, decimal Cost);
public decimal CalculateDiscount(Order order) =>
order switch
{
{ Items: > 10, Cost: > 1000.00m } => 0.10m,
{ Items: > 5, Cost: > 500.00m } => 0.05m,
{ Cost: > 250.00m } => 0.02m,
null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
var someObject => 0m,
};
//如果有 order 有定义了 Deconstruct 析构元组的方法还可以写得更简单
public decimal CalculateDiscount(Order order) =>
order switch
{
( > 10, > 1000.00m) => 0.10m,
( > 5, > 50.00m) => 0.05m,
{ Cost: > 250.00m } => 0.02m,
null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
var someObject => 0m,
};
switch列表模式
可以使用列表模式检查列表或数组中的元素。 列表模式提供了一种方法,将模式应用于序列的任何元素。 此外,还可以应用弃元模式 (_) 来匹配任何元素,或者应用切片模式来匹配零个或多个元素。
当数据不遵循常规结构时,列表模式是一个有价值的工具。 可以使用模式匹配来测试数据的形状和值,而不是将其转换为一组对象。
看看下面的内容,它摘录自一个包含银行交易信息的文本文件:
输出
04-01-2020, DEPOSIT, Initial deposit, 2250.00
04-15-2020, DEPOSIT, Refund, 125.65
04-18-2020, DEPOSIT, Paycheck, 825.65
04-22-2020, WITHDRAWAL, Debit, Groceries, 255.73
05-01-2020, WITHDRAWAL, #1102, Rent, apt, 2100.00
05-02-2020, INTEREST, 0.65
05-07-2020, WITHDRAWAL, Debit, Movies, 12.57
04-15-2020, FEE, 5.55
它是 CSV 格式,但某些行的列数比其他行要多。 对处理来说更糟糕的是,WITHDRAWAL 类型中的一列包含用户生成的文本,并且可以在文本中包含逗号。 一个包含弃元模式、常量模式和 var 模式的列表模式用于捕获这种格式的值处理数据:
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}");
}
前面的示例采用了字符串数组,其中每个元素都是行中的一个字段。 第二个字段的 switch 表达式键,用于确定交易的类型和剩余列数。 每一行都确保数据的格式正确。 弃元模式 (_) 跳过第一个字段,以及交易的日期。 第二个字段与交易的类型匹配。 其余元素匹配跳过包含金额的字段。 最终匹配使用 var 模式来捕获金额的字符串表示形式。 表达式计算要从余额中加上或减去的金额。
列表模式可以在数据元素序列的形状上进行匹配。 使用弃元模式和切片模式来匹配元素的位置。 使用其他模式来匹配各个元素的特征。
C# 8.0引入了属性模式,允许基于对象的属性进行模式匹配
public record Person(string Name,int Age);
var person = new Person("vipwan", 30);
//通俗易懂:如果person不为null,且name==vipwan 并且age>=18的时候
if (person is { Name: "vipwan", Age: >= 18 }) {
Console.WriteLine("vipwan is an adult.");
}
C# 9.0引入的逻辑模式,它允许使用逻辑运算符and、or和not来组合模式。
int? number=null;//支持可空类型
if (number is > 0 and < 10 or 100) {
Console.WriteLine("Number is between 0 and 10 or equals 100.");
}
元组模式允许你对元组的元素进行模式匹配,这在处理元组返回值或多值情况时非常有用
var numbers = (1, "one", 18);
if (numbers is (1, string name, int age)) {
Console.WriteLine($"The name of 1 is {name}, age {age}!");
}
列表模式允许对数组、列表等集合进行模式匹配,可以匹配集合的长度、元素等属性。这对于处理集合数据时进行模式匹配提供了极大的便利。
int[] numbers = { 1, 2, 3 };
if (numbers is [1, 2, 3]) {
Console.WriteLine("The array contains the numbers 1, 2, and 3 in that order.");
}
切片模式允许你匹配集合的一部分,而不是整个集合。这在你只关心集合的某个特定部分时特别有用。
int[] numbers = { 0, 1, 2, 3, 4 };
if (numbers is [0, .., 4]) {
Console.WriteLine("The array starts with 0 and ends with 4.");
}
这里只是介绍了部分好用常见的模式匹配,随着C#语言的逐代增强,可能会有更多的新特性和改进被引入。
参考 https://learn.microsoft.com/zh-cn/dotnet/csharp/fundamentals/functional/pattern-matching