本文最后更新于:2023年11月12日 晚上
模式匹配
看到模式匹配就想起被正则表达式regex
折磨(悲
在C#
中,我们可以使用is
表达式、switch
语句、switch
表达式来将输入表达式与任意数量的特征匹配,最简单的例如:if (sth is TargetClass)
。
就我个人而言,结合在一起的话感觉有点像是switch语法糖。
C#
提供了许多模式匹配,有:声明、类型、常量、关系、逻辑、属性、位置、var、弃元和列表模式等,这里仅对部分模式做出说明。
声明和类型模式 使用声明和类型模式检查表达式的运行时类型是否与给定类型兼容。借助声明模式,还可以声明新的局部变量。例如:
1 2 3 4 5 6 7 8 9 int a = 20 ;if (a is int b) { Console.WriteLine($"a is {b} ." ); }if (a is not null ) { Console.WriteLine($"a is not null." ); }
从C#
9.0开始,可对此使用类型模式,如以下示例所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public abstract class Vehicle {}public class Car : Vehicle {}public class Truck : Vehicle {}public static class TollCalculator { public static decimal CalculateToll (this Vehicle vehicle ) => vehicle switch { Car => 2.00 m, Truck => 7.50 m, null => throw new ArgumentNullException(nameof (vehicle)), _ => throw new ArgumentException("Unknown type of a vehicle" , nameof (vehicle)), }; }
常量模式 可使用常量模式来测试表达式结果是否等于指定的常量,根据匹配的值返回对应的值:
1 2 3 4 5 6 7 8 9 public static decimal GetGroupTicketPrice (int visitorCount ) => visitorCount switch { 1 => 12.0 m, 2 => 20.0 m, 3 => 27.0 m, 4 => 32.0 m, 0 => 0.0 m, _ => throw new ArgumentException($"Not supported number of visitors: {visitorCount} " , nameof (visitorCount)), };
逻辑和关系模式 从C#
9.0开始,可使用关系模式将表达式结果与常量进行比较,当然,你也可以往其中加入逻辑运算符and
、or
、not
来组成更为复杂的模式匹配:
1 2 3 4 5 6 7 8 9 10 11 12 Console.WriteLine(GetCalendarSeason(new DateTime(2021 , 3 , 14 ))); Console.WriteLine(GetCalendarSeason(new DateTime(2021 , 7 , 19 ))); Console.WriteLine(GetCalendarSeason(new DateTime(2021 , 2 , 17 ))); static string GetCalendarSeason (DateTime date ) => date.Month switch { >= 3 and < 6 => "spring" , >= 6 and < 9 => "summer" , >= 9 and < 12 => "autumn" , 12 or (>= 1 and < 3 ) => "winter" , _ => throw new ArgumentOutOfRangeException(nameof (date), $"Date with unexpected month: {date.Month} ." ), };
属性模式 可以使用属性模式将表达式的属性或字段与嵌套模式进行匹配:
1 static bool IsConferenceDay (DateTime date ) => date is { Year: 2020 , Month: 5 , Day: 19 or 20 or 21 };
还可以将运行时类型检查和变量声明添加到属性模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Console.WriteLine(TakeFive("Hello, world!" )); Console.WriteLine(TakeFive("Hi!" )); Console.WriteLine(TakeFive(new [] { '1' , '2' , '3' , '4' , '5' , '6' , '7' })); Console.WriteLine(TakeFive(new [] { 'a' , 'b' , 'c' })); static string TakeFive (object input ) => input switch { string { Length: >= 5 } s => s.Substring(0 , 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." ), };
位置模式 我将其称之为元组模式匹配,因为它确实跟元组有关,使用元组表达式来将多个输入与各种模式进行匹配:
1 2 3 4 5 6 7 8 9 10 11 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.0 m, (>= 5 and < 10 , DayOfWeek.Monday) => 20.0 m, (>= 10 , DayOfWeek.Monday) => 30.0 m, (>= 5 and < 10 , _) => 12.0 m, (>= 10 , _) => 15.0 m, _ => 0.0 m, };
列表模式 从C#11
开始,可以将数组或列表与模式的序列进行匹配:
1 2 3 4 5 6 int [] numbers = { 1 , 2 , 3 }; Console.WriteLine(numbers is [1 , 2 , 3 ]); Console.WriteLine(numbers is [1 , 2 , 4 ]); Console.WriteLine(numbers is [1 , 2 , 3 , 4 ]); Console.WriteLine(numbers is [0 or 1 , <= 2 , >= 3 ]);
如果需要仅匹配开头或结尾的几个元素,可以使用切片模式..
:
1 2 3 4 5 Console.WriteLine(new [] { 1 , 2 , 3 , 4 , 5 } is [> 0 , > 0 , ..]); Console.WriteLine(new [] { 1 , 2 , 3 , 4 } is [.., > 0 , > 0 ]); Console.WriteLine(new [] { 1 , 2 , 3 , 4 } is [>= 0 , .., 2 or 4 ]);
还可以在切片模式中嵌套子模式(比如嵌套逻辑模式什么的)。
运算符 其实这里主要记录一下对我来说比较陌生的运算符(或者说之前没学过的),所以会不全,要全请前往官方文档捏~
!
(null
包容)运算符一元后缀!
运算符是null
包容运算符或null
抑制运算符。在已启用的可为空的注释上下文中,使用null
包容运算符来取消上述表达式的所有可为null
警告。
??
和??=
运算符——Null合并操作符先从??
运算符(null
合并运算符)说起。如果左操作数的值不为null
,则返回该值;否则,它会计算右操作数并返回其结果。如果左操作数的计算结果为非null
,则??
运算符不会计算其右操作数。
1 2 int ? a = null ; Console.WriteLine((a ?? 3 ));
然后是??=
运算符(null
合并赋值运算符)。仅当左操作数的计算结果为null
时,??=
才会将其右操作数的值赋值给其左操作数。如果左操作数的计算结果为非null
,则??=
不会计算其右操作数。
1 2 3 4 int ? a = null ; Console.WriteLine((a is null )); a ??= 3 ; Console.WriteLine(a);
Null条件运算符?.
和?[]
仅当操作数的计算结果为非null
时,null
条件运算符才对其操作数应用成员访问(?.
)或元素访问(?[]
)操作;否则,它会返回null
。即:
如果a
的计算结果为null
,则a?.x
或a?[x]
的结果为null
。
如果a
的计算结果为非null
,则a?.x
或a?[x]
的结果将分别与a.x
或a[x]
的结果相同。
::
运算符 - 命名空间别名运算符使用命名空间别名限定符::
访问已设置别名的命名空间的的成员,比如:
1 2 3 4 5 6 7 using forwinforms = System.Drawing;using forwpf = System.Windows;public class Converters { public static forwpf::Point Convert (forwinforms::Point point ) => new forwpf::Point(point.X, point.Y); }
希望本文章能够帮到您~