C#7中的模式匹配

C#7终于有了期待已久的功能,称为模式匹配。 如果您熟悉诸如F#之类的功能语言,那么此功能以当前存在的形式可能会使您有些失望。 但是即使在今天,它也可以在各种情况下简化代码。 削减更多细节!



每个新功能对于开发人员而言对于性能至关重要的应用程序来说都是危险的。 新的抽象级别是好的,但是要有效地使用它们,您需要了解它们的实际工作方式。 本文讨论了模式匹配功能及其工作原理。

C#中的示例可以在is表达式以及switch语句的case块中使用。
共有三种类型的样本:

  • 样本常数
  • 类型样本
  • 样本变量。

表达式中的模式匹配


public void IsExpressions(object o) { // Alternative way checking for null if (o is null) Console.WriteLine("o is null"); // Const pattern can refer to a constant value const double value = double.NaN; if (o is value) Console.WriteLine("o is value"); // Const pattern can use a string literal if (o is "o") Console.WriteLine("o is \"o\""); // Type pattern if (o is int n) Console.WriteLine(n); // Type pattern and compound expressions if (o is string s && s.Trim() != string.Empty) Console.WriteLine("o is not blank"); } 

使用is表达式,您可以检查该值是否恒定,使用类型检查,您还可以确定样本变量。

在is表达式中使用模式匹配时,应注意以下几点:

  • if语句输入的变量将发送到外部作用域。
  • 仅当模式匹配时,才显式分配if语句输入的变量。
  • 表达式中模式匹配的当前实现不是很有效。

首先,请考虑前两种情况:

 public void ScopeAndDefiniteAssigning(object o) { if (o is string s && s.Length != 0) { Console.WriteLine("o is not empty string"); } // Can't use 's' any more. 's' is already declared in the current scope. if (o is int n || (o is string s2 && int.TryParse(s2, out n))) { Console.WriteLine(n); } } 

第一个if语句引入变量s,该变量在整个方法内部可见。 这是合理的,但是如果同一块中的其他if语句尝试重用相同的名称,则会使逻辑复杂化。 在这种情况下,请确保使用其他名称以避免冲突。

仅当谓词为true时,才显式分配is表达式中输入的变量。 这意味着第二个if语句中的变量n没有在正确的操作数中分配,但由于已经声明,因此可以将其用作int.TryParse方法中的out变量。

上面提到的第三点是最重要的。 考虑以下示例:

 public void BoxTwice(int n) { if (n is 42) Console.WriteLine("n is 42"); } 

在大多数情况下,表达式将转换为object.Equals(常量,变量)[尽管特性表明运算符==应该用于简单类型]:

 public void BoxTwice(int n) { if (object.Equals(42, n)) { Console.WriteLine("n is 42"); } } 

此代码调用两个打包转换过程,如果在关键应用程序路径上使用它们会严重影响性能。 以前,如果o变量为可为null的类型,则o为称为包装的null表达式(请参阅e的次优代码为null ),但希望此问题将得到解决(这是对应的请求在github上 )。

如果变量n是对象类型,则表达式o为42将导致一个“打包转换”过程(对于文字42),尽管基于switch语句的类似代码不会导致这种情况。

表达式中的样本变量


可变模式是一种特殊的模式类型,区别很大:模式将匹配任何值,甚至为null。

 public void IsVar(object o) { if (o is var x) Console.WriteLine($"x: {x}"); } 

如果o不为null,则表达式o is object将为true,但是表达式o is var x将始终为true。 因此,处于发布模式的编译器*完全排除了if语句,而仅保留Console方法调用。 不幸的是,在以下情况下,编译器不会警告代码不可用:if(!(O是var x))Console.WriteLine(“ Unreachable”)。 希望这一问题也将得到解决。

*尚不清楚为什么行为仅在发布模式下有所不同。 似乎所有问题的根源都是相同的:函数的初始实现不是最佳的。 但是,根据Neal Gafter的评论 ,一切将很快改变:“与示例匹配的代码将被重新编写(以支持递归示例)。 我认为您正在谈论的大多数改进将在新代码中实现,并且可以免费获得。 但是,这将需要一些时间。”

没有空检查将使这种情况变得特殊,并可能导致危险。 但是,如果您确切地知道此示例的工作方式,那么它将很有用。 它可以用来在表达式中引入一个临时变量:

 public void VarPattern(IEnumerable<string> s) { if (s.FirstOrDefault(o => o != null) is var v && int.TryParse(v, out var n)) { Console.WriteLine(n); } } 

是表达和猫王的声明


还有另一种情况可能被证明是有用的。 样本类型仅在不为空时匹配值。 我们可以将这种“过滤”逻辑与null运算符结合使用,以使代码更具可读性:

 public void WithNullPropagation(IEnumerable<string> s) { if (s?.FirstOrDefault(str => str.Length > 10)?.Length is int length) { Console.WriteLine(length); } // Similar to if (s?.FirstOrDefault(str => str.Length > 10)?.Length is var length2 && length2 != null) { Console.WriteLine(length2); } // And similar to var length3 = s?.FirstOrDefault(str => str.Length > 10)?.Length; if (length3 != null) { Console.WriteLine(length3); } } 

注意,相同的模式可用于值类型和引用类型。

案例块中的模式匹配


switch语句的功能已在C#7中进行了扩展,因此现在可以在case子句中使用模式:

 public static int Count<T>(this IEnumerable<T> e) { switch (e) { case ICollection<T> c: return c.Count; case IReadOnlyCollection<T> c: return c.Count; // Matches concurrent collections case IProducerConsumerCollection<T> pc: return pc.Count; // Matches if e is not null case IEnumerable<T> _: return e.Count(); // Default case is handled when e is null default: return 0; } } 

本示例显示了switch语句的第一组更改。

  1. 任何类型的变量都可以与switch语句一起使用。
  2. case子句允许您指定模式。
  3. case子句的顺序很重要。 如果前一个句子对应于基本类型,而下一个句子对应于派生类型,则编译器将引发错误。
  4. 隐式检查自定义商品是否为空**。 在上面的示例中,last case子句有效,因为它仅在参数不为null时才匹配。

**案例的最后一句显示了C#7中添加的另一个函数-空变量的样本。 特殊名称_告诉编译器不需要该变量。 case子句中的类型样本需要别名。 但是,如果不需要它,可以使用_。

以下代码段显示了基于switch语句的模式匹配的另一个功能-使用谓词的能力:

 public static void FizzBuzz(object o) { switch (o) { case string s when s.Contains("Fizz") || s.Contains("Buzz"): Console.WriteLine(s); break; case int n when n % 5 == 0 && n % 3 == 0: Console.WriteLine("FizzBuzz"); break; case int n when n % 5 == 0: Console.WriteLine("Fizz"); break; case int n when n % 3 == 0: Console.WriteLine("Buzz"); break; case int n: Console.WriteLine(n); break; } } 

这是FizzBu​​zz任务的一个奇怪版本,它处理一个对象,而不仅仅是一个数字。

switch语句可以包含多个相同类型的case子句。 在这种情况下,编译器将所有类型检查结合起来以避免不必要的计算:

 public static void FizzBuzz(object o) { // All cases can match only if the value is not null if (o != null) { if (o is string s && (s.Contains("Fizz") || s.Contains("Buzz"))) { Console.WriteLine(s); return; } bool isInt = o is int; int num = isInt ? ((int)o) : 0; if (isInt) { // The type check and unboxing happens only once per group if (num % 5 == 0 && num % 3 == 0) { Console.WriteLine("FizzBuzz"); return; } if (num % 5 == 0) { Console.WriteLine("Fizz"); return; } if (num % 3 == 0) { Console.WriteLine("Buzz"); return; } Console.WriteLine(num); } } } 

但是有两件事要牢记:

1.编译器仅结合顺序类型检查,并且如果将case子句与不同类型混合使用,则会生成质量较低的代码:

 switch (o) { // The generated code is less optimal: // If o is int, then more than one type check and unboxing operation // may happen. case int n when n == 1: return 1; case string s when s == "": return 2; case int n when n == 2: return 3; default: return -1; } 

编译器将其转换如下:

如果(o是int n && n == 1)返回1;
 if (o is string s && s == "") return 2; if (o is int n2 && n2 == 2) return 3; return -1; 

2.编译器会尽一切可能避免典型的排序问题。

 switch (o) { case int n: return 1; // Error: The switch case has already been handled by a previous case. case int n when n == 1: return 2; } 

但是,编译器无法确定一个谓词强于另一个谓词,因此无法有效替换以下case子句:

 switch (o) { case int n when n > 0: return 1; // Will never match, but the compiler won't warn you about it case int n when n > 1: return 2; } 

模式匹配简介


  • 在C#7中出现了以下模式:恒定模式,类型模式,可变模式和空可变模式。
  • 可以在is表达式和case块中使用样本。
  • 表达式中常量模式的实现是针对值类型的,在性能方面远非理想。
  • 变量的样本始终匹配,必须谨慎对待它们。
  • switch语句可用于在when子句中设置带有附加谓词的类型检查。

莫斯科Unity活动-Unity Moscow Meetup 2018.1


10月11日,星期四,Unity Moscow Meetup 2018.1将在高等经济学院举行。 这是本季度在莫斯科举行的Unity开发人员的第一次会议。 第一个mitap的主题将是AR / VR。 您会发现有趣的报告,与行业专家的交流以及MSI的特殊演示区。

详细资料

Source: https://habr.com/ru/post/zh-CN423229/


All Articles