概述:is,switch

C#从7.0开始,陆陆续续推出了各种模式匹配,模式是一种特殊的表达式,通过判断给定的值是否满足此表达式而返回true或者false,它就类似于正则表达式的作用。

  • 目前(C#10),可以使用模式匹配的地方有三个:

    • 1、is表达式,从C#7.0开始,is表达式的右边不在只是一个类型,而是一个模式,具体例子可见:C#中的is和as的用法
    • 2、switch语句
    • 3、switch表达式。从C#7.0开始,每个case语句不在是一个值,而是一个模式,具体例子可见:C#的switch的用法
  • 现在,C#9.0版本后,已存在9中匹配模式:声明模式、类型模式、常量模式、关系模式、逻辑模式、属性模式、位置模式、var 模式、弃元模式。

声明模式

  • 声明模式用于与检测表达式运行时的类型是否与指定的类型相匹配,如果匹配成功,则将匹配的结果给到指定声明的变量。
  • 匹配判断成功的条件:

    • 表达式运行时的类型与模式指定类型相同
    • 表达式运行时的类型派生自模式指定类型,或者实现模式指定模型接口,换句话说,就是表达式运行时的类型可通过隐式引用装换成模式指定类型,隐式引用装换可参考C#中的隐式转换
    • 表达式运行时的类型是模式指定类型的可为null的值类型
    • 表达式运行时的类型可通过装箱或者取消装箱转换成模式指定类型
  • 一个简单的例子:

  1. static void Main(string[] args)
  2. {
  3. {
  4. //1.表达式运行时的类型与模式指定类型相同
  5. object obj = new Exception(nameof(Exception));
  6. if (obj is Exception exception)
  7. {
  8. Console.WriteLine(exception.Message);
  9. }
  10. }
  11. {
  12. //2.表达式运行时的类型可通过隐式引用装换成模式指定类型
  13. object obj = new ArgumentNullException();
  14. if (obj is Exception exception)
  15. {
  16. Console.WriteLine(exception.Message);
  17. }
  18. }
  19. {
  20. //3.表达式运行时的类型是模式指定类型的可为null的值类型
  21. int? nullable_i = 1;
  22. if (nullable_i is int i)
  23. {
  24. Console.WriteLine(i);
  25. }
  26. }
  27. {
  28. //4.表达式运行时的类型可通过装箱或者取消装箱转换成模式指定类型
  29. object obj = true;
  30. if (obj is bool b)
  31. {
  32. Console.WriteLine(b);
  33. }
  34. }
  35. }

类型模式

  从C#9.0开始支持类型模式,类型模式是声明模式的一种简单化用法,只是检测表达式运行时的类型是否与指定的类型相匹配,而不需要将匹配成功后的结果给到一个变量,如:

  1. static void Print(object value)
  2. {
  3. switch (value)
  4. {
  5. case int: Console.WriteLine("value is int"); break;
  6. case double: Console.WriteLine("value is double"); break;
  7. case string: Console.WriteLine("value is string"); break;
  8. case bool: Console.WriteLine("value is bool"); break;
  9. case object: Console.WriteLine("value is object"); break;
  10. }
  11. }

常量模式

  常量模式用于测试表达式结果是否等于指定常量,这几乎就是C#6.0及之前版本中switch-case的用法,它可指定的常量类型包括:

  • 整型(int、long、byte等)和浮点型(float、double、decimal等)
  • 字符(char)和字符串(string)
  • 布尔值 true 或 false
  • 枚举类型值
  • const声明的常量
  • null

  • 例如:  

  1. const string STR = "STR";
  2. static void Print(object value)
  3. {
  4. switch (value)
  5. {
  6. case 1: Console.WriteLine(1); break;
  7. case 1.1: Console.WriteLine(1.1); break;
  8. case "string": Console.WriteLine("string"); break;
  9. case true: Console.WriteLine(true); break;
  10. case false: Console.WriteLine(false); break;
  11. case DayOfWeek.Saturday:
  12. case DayOfWeek.Sunday: Console.WriteLine("weekend"); break;
  13. case STR: Console.WriteLine(STR); break;
  14. case null: Console.WriteLine("null"); break;
  15. }
  16. }
  • 注:对null的检测不会调用用户重载的相等运算符 ==

关系模式

  关系模式用于将表达式结果与指定常量表达式进行比较,可以使用的关系运算符有:<、>、<= 、>=,而右边的常量表达式只能是:整型(int、long、byte等)、浮点型(float、double、decimal等)、字符(char)、枚举类型值。 

  1. static void Print(double value)
  2. {
  3. switch (value)
  4. {
  5. case < 0: Console.WriteLine("value is negative"); break;
  6. case 0: Console.WriteLine("value is 0"); break;
  7. case > 0: Console.WriteLine("value is positive"); break;
  8. case double.NaN: Console.WriteLine("value is NaN"); break;
  9. }
  10. }

逻辑模式:not 0 and (100 or -100);< -100 or (< 0 and > -100)

  模式可以通过逻辑关系进行组合,逻辑模式用于检测表达式的结果是否与组合的模式匹配。组合逻辑使用关键字:and、or、not,例如:

  1. static void Print(object value)
  2. {
  3. switch (value)
  4. {
  5. case 0: Console.WriteLine("value is 0"); break;
  6. case not 0 and (100 or -100): Console.WriteLine("abs(value)==100"); break;
  7. case not 0 and (> 0 and < 100): Console.WriteLine("value is positive and less than 100"); break;
  8. case not 0 and > 0: Console.WriteLine("value is positive and greater than 100"); break;
  9. case < -100 or (< 0 and > -100): Console.WriteLine("value is negative and not equals -100"); break;
  10. }
  11. }

  注:and 优先级高于 or,我们可以使用括号来改变优先级

属性模式:{ Year: 2021, Month: 7, Day: 12 }

  • 属性模式用于提取表达式结果的属性或者字段,用于检测表达式结果是否满足指定的属嵌套模式匹配相匹配。
  • 其实形式上,属性模式使用属性或者字段作为匹配对象,比如:  
  1. static void Main(string[] args)
  2. {
  3. DateTime time = DateTime.Now;
  4. switch (time)
  5. {
  6. case { Year: 2021, Month: 7, Day: 12 } t:Console.WriteLine($"Year: {t.Year}, Month: {t.Month}, Day: {t.Day}");break;
  7. case { Year: 2021, Month: 7 }: Console.WriteLine($"Year: {time.Year}, Month: {time.Month}"); break;
  8. case { Date: { Year: 2021, Month: 7 } }: Console.WriteLine($"recursion"); break;//使用嵌套递归
  9. }
  10. }
  • 可以看到,属性模式用于说明表达式的结果是否具有指定的结构,且对应的属性应该满足相对应的条件。 
  • 另外,需要注意的是,匹配的每个属性及字段的值是一个匹配模式,如:  
  1. static void Main(string[] args)
  2. {
  3. DateTime time = DateTime.Now;
  4. switch (time)
  5. {
  6. case { Year: 2020 or 2021, Month: <= 6, Day: 1 } t: Console.WriteLine($"the first day of every month in the first half of 2020 and 2021"); break;
  7. case { Year: not 2022 }: Console.WriteLine($"not 2022"); break;
  8. case { DayOfWeek: not DayOfWeek.Sunday and not DayOfWeek.Saturday }: Console.WriteLine($"recursion"); break;
  9. }
  10. }
  • 基于这一点,属性模式可以和声明模式、类型模式等进行组合,用于对属性、字段等进行条件匹配:  
  1. DateTime time = DateTime.Now;
  2. if (time is DateTime { Year: >= 2021 })
  3. {
  4. //something
  5. }

位置模式:point is (> 0, > 0)

  位置模式的作用与属性模式一样,只是位置模式借助元组(解构表达式)的相关特性来提取属性或者字段,用于检测表达式结果是否满足指定的属嵌套模式匹配相匹配。

  同时,属性模式中,会要求模式中的属性在匹配对象中显式的存在,否则编译报错,而位置模式因为基于解构,因此没有这种限制。

  需要注意的是,元组中接收的每一项都是模式,如:  

  1. public class Point2D
  2. {
  3. public int X { get; set; }
  4. public int Y { get; set; }
  5. public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
  6. }
  7. static void Print(object point)
  8. {
  9. if (point is (> 0, > 0))
  10. {
  11.         //something
  12. }
  13. }
  • 在使用方面,位置模式可以同声明模式、类型模式、属性模式等结合使用:  
  1. public class Point2D
  2. {
  3. public int X { get; set; }
  4. public int Y { get; set; }
  5. public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
  6. }
  7. public class Point3D
  8. {
  9. public int X { get; set; }
  10. public int Y { get; set; }
  11. public int Z { get; set; }
  12. public void Deconstruct(out int x, out int y, out int z) => (x, y, z) = (X, Y, Z);
  13. }
  14. static void Print(object point)
  15. {
  16. if (point is Point2D(> 0, > 0))
  17. {
  18. //something
  19. }
  20. if (point is Point3D(> 0, > 0, > 0) { X: < 100, Y: < 100, Z: < 100 })
  21. {
  22. //something
  23. }
  24. }

Var模式

  • var模式用户提取属性模式、位置模式等模式中的属性、字段等数据到指定的变量中,从而可以进行进一步的匹配或者使用。
  • 例如:  
  1. public class Point2D
  2. {
  3. public int X { get; set; }
  4. public int Y { get; set; }
  5. public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
  6. }
  7. static void Print(object point)
  8. {
  9. //位置模式中使用var
  10. //(var x, var y)也可以写成 var (x, y)
  11. if (point is (var x, var y))
  12. {
  13. Console.WriteLine($"X:{x} Y:{y}");
  14. }
  15. //属性模式中使用var
  16. if (point is Point2D { X: var a, Y: var b })
  17. {
  18. Console.WriteLine($"X:{a} Y:{b}");
  19. }
  20. }

弃元模式

  • 弃元模式使用下划线(_)作为变量,告诉编译器,这个变量被丢弃了,不再使用(这个弃元可能来源于golang中弃元的思想)。
  • 弃元很有用,比如类型模式等价于在声明模式中声明弃元:  
  1. static void Main(string[] args)
  2. {
  3. object obj = 1;
  4. bool b = obj is int;
  5. //等价于
  6. bool b = obj is int _;
  7. }
  • 在位置模式和属性模式中也可以使用弃元:  
  1. public class Point2D
  2. {
  3. public int X { get; set; }
  4. public int Y { get; set; }
  5. public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
  6. }
  7. static void Print(object point)
  8. {
  9. //位置模式中使用弃元
  10. //(var x, _)也可以写成 var (x, _)
  11. if (point is (var x, _))
  12. {
  13. Console.WriteLine($"X:{x}");
  14. }
  15. //属性模式中使用弃元
  16. if (point is Point2D { X: var a, Y: _ })
  17. {
  18. Console.WriteLine($"X:{a}");
  19. }
  20. }
  • 特别是元组,因为解构表达式的存在,因此在解构时,不得不接收每一项,而有时我们可能只关心其中的几项,这时弃元就爬上用场了:
  1. (var a, _) = new Tuple<int, int>(1, 2);
  2. (_, var y) = new Point2D();