来源: C#.Net筑基-模式匹配汇总 – 安木夕 – 博客园
01、模式匹配概述
从C#7
开始支持的 模式匹配 语法(糖,挺甜),可非常灵活的对数据进行条件匹配和提取,经过多个版本的完善,已经非常强大了。
C# 支持多种模式,包括声明、类型、常量、关系、属性、列表、var 和弃元等,在is
、switch
语句、switch
表达式中使用,还可以使用布尔逻辑关键字 and
、or
和 not
组合多个模式,极大的简化了代码编写,可读性也还不错。
标题 | 说明 | 示例/备注 |
---|---|---|
类型和声明模式 | 如果类型兼容,则申明并赋值变量 | if (age is int i) |
常量模式 | 检查表达式值是否等于、不等于(not)常量值 | if(age is null || age is 0) |
关系模式>< |
使用关系运算符< 、> 、<= 或 >= 匹配 |
case 0 or <=6 |
逻辑模式 | 用not >and >or 连接多个模式表达式 |
case < 12 and ( >6 or 6) |
属性模式{:} |
对实例的属性、字段进行模式匹配:{属性/字段:匹配模式} |
if (b is { Year: < 2000, Month: 1 or 11 }) |
位置模式(解构) | 基于解构赋值进行模式匹配:(解构参数) |
if(point is (_,>0,>0)) |
var 模式 | 用var 申明(捕获)任意局部变量 |
if(point is var p && p.X>0) |
弃元模式_ | 弃元模式 _ 来匹配任何(其他)表达式 |
表示不要的 |
列表模式[] | 对数组(列表)进行匹配,在中括号[] 中匹配列表中的项 |
if(numbers is [_, 2, 3, ..]) |
📢 模式匹配基本都是语法糖,味道都不错!C#在编译时会输出原本的基础代码,可通过 https://sharplab.io/ 在线查看编译后的代码。
02、模式匹配
2.1、类型和声明模式
检查类型是否匹配,同时申明变量,如果类型兼容则申明并赋值变量,该变量在后面代码作用域中有效。
object age = “123”; | |
if (age is int i) //类型匹配+申明变量i | |
{ | |
Console.WriteLine($”age1 = {i}“); | |
} | |
switch (age) | |
{ | |
case string: //类型匹配 | |
Console.WriteLine($”type is string”); | |
break; | |
case int iage: //类型匹配+申明变量iage | |
Console.WriteLine($”age2 = {iage}“); | |
break; | |
} | |
//上面is语句编译后的代码效果: | |
if (obj is int) | |
{ | |
int value = (int)obj; | |
} |
2.2、常量模式
检查表达式值是否等于、不等于(not
)常量值,常量值包括字面量常量,也包括const
常量值。传统的Switch
语句就是常量模式匹配。
object age = null; | |
if (age is not null && age is 100) //age is 100 等同于 age is int && (int)age==100 | |
{ | |
Console.WriteLine($”age1 = {age}“); | |
} | |
var type = age switch{ | |
1 or 2 or 3=>“婴儿”, | |
4 => “幼儿”, | |
null or not 5 => “unknow”, | |
_=>“”, | |
}; |
2.3、关系模式><
用关系运算符来匹配表达式,就是对常量数值进行大小比较运算,使用关系运算符<
、>
、<=
或 >=
,多个表达式可用and
、or
连接,当然也支持括号。
object age = 6; | |
if (age is int n and >= 6) | |
{ | |
Console.WriteLine(“666”); | |
} | |
switch (age) | |
{ | |
case 0 or <=6: | |
Console.WriteLine(“幼儿”); | |
break; | |
case < 12 and ( >6 or 6): | |
Console.WriteLine(“青少年”); | |
break; | |
} |
2.4、逻辑模式not
/and
/or
用 not
、and
和 or
模式连结符来创建逻辑模式,连接多个模式表达式。
- 优先级顺序:
not
>and
>or
。 - 推荐使用
(括号)
显示控制优先顺序,可读性更好。
object age = 6; | |
if (age is int n and (not 6 or >5) ) | |
{ | |
Console.WriteLine(“666”); | |
} |
2.5、属性模式{:}
对实例的属性、字段进行模式匹配,可以嵌套其他模式匹配,非常的强大,属性匹配用大括号来包装{属性/字段:匹配模式}
。
- 多个属性/字段都匹配为
true
时,最终才会匹配成功。 - 可以结合类型申明模式使用。
- 可嵌套使用,会递归匹配。
DateTime birthday = new DateTime(1999, 11, 12); | |
if (birthday is { Year: < 2000, Month: 1 or 11 }) | |
{ | |
Console.WriteLine(“年龄、星座不合适”); | |
} | |
//嵌套使用 | |
public record Point(int X, int Y); | |
public record Segment(Point Start, Point End); | |
static bool IsAnyEndOnXAxis(Segment segment) => | |
segment is { Start: { Y: 0 } } or { End: { Y: 0 } }; | |
static bool IsAnyEndOnXAxis(Segment segment) => | |
segment is { Start.Y: 0 } or { End.Y: 0 }; |
2.6、位置模式(解构)
基于解构赋值进行模式匹配:
Tuple
、record 和 DictionaryEntry是内置支持解构的,关于解构赋值可参考相关内容。- 用括号
()
报装,这也是 解构(Deconstruct)的语法形式。 - 可以嵌套其他模式匹配,如常量、关系、逻辑、属性模式等。
void Main() | |
{ | |
Point point = new Point(“sam”, 12, 13); | |
var len = point switch | |
{ | |
//类型匹配、属性模式、位置模式:Name属性必须为string,且长度为0,X、Y值为0 | |
(string { Length: <= 0 }, 0, 0) => 0, | |
(_, > 0, 0) => point.X, //X值大于0,Y值为0 | |
(_, 0, > 0) => point.Y, //Y值大于0,X值为0 | |
(_, 10 or > 10, 10 or > 10) p => p.X * p.Y, | |
_ => 0, | |
}; | |
} | |
public record Point(string Name, int X, int Y); |
2.7、var 模式
用var
申明(捕获)任意局部变量,把表达式的结果分配给var
临时变量。算是类型模式的变种,将类型名替换成了var
。
void Main() | |
{ | |
Point point = new Point(“sam”, 12, 13); | |
if(point is var p && p.X>0 && p.Y>0){ //is var | |
Console.WriteLine(“OK”); | |
} | |
var len = point switch | |
{ | |
var (_,x,y) when x>0 && y>0 => true,// var | |
}; | |
} | |
public record Point(string Name, int X, int Y); |
2.8、弃元模式_
弃元模式(Discard Pattern),字面理解就是被遗弃、没人要的。可以将弃元模式看做是一个占位符,表示一个没人用的变量,可匹配任意类型,用来简化代码。语法是用下划线“_
”表示。
常用场景:
- 1、解构时的占位符。
- 2、在
Switch
中匹配任意其他模式,类似default
的作用。 - 3、在
out
参数中占位,表示一个没人用的out
参数。 - 4、独立弃元,接收无用的表达式输出。
var tuple = new Tuple<int, int>(3, 4); | |
var (x, _) = tuple; //1、只需要第一个参数,其他就用“_”来占位 | |
Console.WriteLine(x); //3 | |
_= x switch | |
{ | |
2 or <2 => “small”, | |
int and <18=>“young”, | |
_=>“other”, //2、匹配其他模式,效果同default | |
}; | |
int.TryParse(“”, out _); //3、不用的out变量,实际上是申明了变量的 | |
async void Print(object arg) | |
{ | |
_ = arg ?? throw new ArgumentException(); //4、接收无用的返回,效果同下 | |
if (arg == null) throw new ArgumentException(); | |
_ = Task.Run(()=>Console.WriteLine(“task run”)); //接收一个不用的返回 | |
} |
弃元模式_
是一个提供给编译器用的符号,告诉编译这个变量不用了,编译器会根据情况进行优化处理。在对out
参数使用时,编译器会自动创建变量,如下代码:
int.TryParse(“”,out _); | |
//实际编译后的代码如下 | |
int result; | |
int.TryParse(“”, out result); |
📢需要注意的是 下划线
_
是并不是一个关键字,也能当做参数名来使用,不要混用。
2.9、列表模式[]
C#11支持的,对数组(列表)进行匹配,在中括号[]
中匹配列表中的项。
- 跳过的项可以用弃元模式
_
。 - 可以用数组的切片模式匹配开头、结尾的元素。
void Main() | |
{ | |
int[] numbers = { 1, 2, 3, 4 }; | |
Console.WriteLine(numbers is [_, 2, 3, ..]); // True | |
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]); // False | |
} |
03、模式匹配应用
上面的各种模式匹配主要就用在 is 运算符、switch 语句、switch 表达式 中。
3.1、is运算符
is 运算符 本来主要是用来检测类型兼容性的,加上模式匹配就能玩出各种花样了,极大简化了让各种检查类的代码。
object value = 12; | |
if (value is int && value is not null) //is类型检测+逻辑模式 | |
{ | |
Console.WriteLine(value); | |
} | |
if (value is int a && a > 6) //+申明模式 | |
{ | |
Console.WriteLine(a); | |
} | |
if (value is int age and > 10 and < 14) //关系模式 | |
{ | |
Console.WriteLine(age); | |
} | |
var user = new { Name = “sam”, Age = 12 }; | |
if (user is { Name: _, Age: > 10 }) //属性模式 | |
{ | |
Console.WriteLine(user.Name); | |
} | |
int[] arr = new int[] { 1, 2, 3 }; | |
if (arr is [> 0, ..]) //列表模式:第一个元素>0 | |
{ | |
Console.WriteLine(arr); | |
} | |
var dt = new Tuple<string, int>(“sam”, 100); | |
if (dt is (_, > 60) d) //位置模式+申明模式(好像没什么用) | |
{ | |
Console.WriteLine(d.Item1); | |
} | |
3.2、switch..case语句
switch..case 语句 是很多语言中都有的基本多条件分支语句,传统的 case 只能用于匹配常量,多用于枚举。
case
不能穿透,一个case
执行完后必须break
结束,或者return
返回(退出方法),可以多个case
匹配执行一组逻辑代码。- 传统的
case
就是常量模式,而现代的case
可以结合上面多种模式使用,非常强大。 - 加
when
,自由附加更多条件。
int age = 22; | |
string sex = “Male”; | |
switch (age) | |
{ | |
case 1: | |
case 2: | |
Console.WriteLine(“婴儿”); | |
break; | |
case <= 3: | |
Console.WriteLine(“幼儿”); | |
break; | |
case > 10 and < 16: | |
Console.WriteLine(“青少年”); | |
break; | |
case > 18 when sex == “Male”: | |
Console.WriteLine(“成年男性”); | |
break; | |
case int: | |
break; | |
} |
3.3、switch表达式
C#8
中switch
有了新的语法 —— switch 表达式 ,可以看做是switch..case
语句的一个变种,使用比较类似。switch
表达式是一个赋值(输出)语句。
=>
左侧为模式(返回一个bool),如果模式匹配(true)则返回右侧的值,最后一个弃元模式匹配其他情况,同default
效果。
int type = 6; | |
var message = type switch | |
{ | |
<= 1 => “success”, | |
2 => “warning”, | |
3 => “error”, | |
> 3 and < 10 => “other error”, | |
_ => “unkonwn error”, | |
}; |
可以用when
来进行更多的判断,when
后面的表达式就很自由了,只要返回boo
即可。
object type = 6; | |
var message = type switch | |
{ | |
int i when i<6 => “ok”, | |
string s when s==“null”=>“Null”, | |
string s when !string.IsNullOrEmpty(s)=>“string value”, | |
_=>“unknown value” | |
}; | |
Console.WriteLine(message); |
支持多个变量的组合模式:用括号()
包含多个变量
string gender = “male”; | |
int age = 10; | |
string type = (gender,age) switch{ | |
(“male”,>18)=>“VIP”, | |
(not “male”,>26 and <35)=>“VVIP”, | |
_=>“”, | |
}; |
参考资料
©️版权申明:版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处!原文编辑地址-语雀