Pattern matching in C # 7
In C # ? a long-awaited feature finally appeared called pattern matching. If you are familiar with functional languages, such as F #, then this function in the form in which it currently exists may slightly disappoint you. But even today it can simplify the code in a variety of cases. More under the cut!
Each new feature can be dangerous for a developer building an application for which performance is critical. New levels of abstractions are good, but to effectively use them, you need to understand how they actually work. This article discusses the pattern matching function and how it works.
The pattern in C # can be used in the is expression, as well as in the case block of the switch statement.
There are three types of samples:
sample constants; 3r33473.
sample type; 3r33473.
sample variable. 3r33473.
3r33475.
3r38080. Comparison with the sample in the expressions is
3r3444. 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");
} 3r33450.
Using the is expression, you can check whether the value is constant, and using the type check you can additionally determine the sample variable.
When using pattern matching in is expressions, you should pay attention to a few interesting points:
The variable entered by the if statement is sent to the outer scope. 3r33473.
The variable entered by the if statement is explicitly assigned only when the sample falls. 3r33473.
The current implementation of pattern matching of a constant in is expressions is not very efficient. 3r33473.
3r33475.
First consider the first two cases:
3r3444. 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 (s? out n)))
{
Console.WriteLine (n);
}
} 3r33450.
The first if statement introduces the variable s, visible inside the entire method. This is reasonable, but it will complicate the logic if other if expressions in the same block try to reuse the same name. In this case, be sure to use a different name to avoid conflicts.
The variable entered in the is expression is explicitly assigned only when the predicate is true. This means that the variable n in the second if expression is not assigned in the right-hand operand, but since it is already declared, we can use it as the out variable in the int.TryParse method.
The third point mentioned above is the most important. Consider the following example:
3r3444. public void BoxTwice (int n)
{
if (n is 42) Console.WriteLine ("n is 42");
} 3r33450.
In most cases, the expression is is converted to object.Equals (constant, variable)[хотя в характеристиках говорится, что для простых типов следует использовать оператор ==]:
3r3444. public void BoxTwice (int n)
{
if (object.Equals (4? n))
{
Console.WriteLine ("n is 42");
}
} 3r33450.
This code triggers two packaging-transformation processes that can significantly affect performance if used on the critical path of the application. Previously, the expression o is null caused packing if the variable o had a type that is null-capable (see Suboptimal code for e is null (“Non-optimal code for e is null”)), but there is hope that this will be fixed (here is the corresponding 3r3-350. request for github
).
If the variable n is of type object, then the expression o is 42 will invoke a single wrapping-transform process (for literal 42), although a similar code based on the switch statement would not lead to this.
3r38080. The sample variable in the expression is
A sample variable is a special kind of sample type with one big difference: the sample will match any value, even null.
3r3444. public void IsVar (object o)
{
if (o is var x) Console.WriteLine ($ "x: {x}");
} 3r33450.
The expression o is object will be true if o is not null, but the expression o is var x will always be true. Therefore, the compiler in release mode * completely eliminates if statements and simply leaves the call to the Console method. Unfortunately, the compiler does not warn about the inaccessibility of the code in the following case: if (! (O is var x)) Console.WriteLine (“Unreachable”). It is hoped that this too will be fixed.
3r33232. * It is unclear why behavior differs only in release mode. It seems that the root of all the problems is the same: the initial implementation of the function is not optimal. However, judging by 3r3r2182. this comment
Neal Gafter, everything will change soon: “The code for pattern matching will be rewritten from scratch (to also support recursive patterns). I think that most of the improvements that you are talking about will be implemented in the new code and are available for free. However, this will take some time. ” 3r33300.
The absence of a null check makes this situation special and potentially dangerous. However, if you know the exact principles of operation of this sample, then it may be useful. It can be used to enter a temporary variable in the expression:
3r3444. public void VarPattern (IEnumerable
s)
{
if (s.FirstOrDefault (o => o! = null) is var v
&& int.TryParse (v, out var n))
{
Console.WriteLine (n);
}
} 3r33450.
3r38080. The expression Is and the operator Elvis
There is another case that may be useful. A sample type corresponds to a value only when it is not null. We can use this “filtering” logic with a null propagation operator to make the code more readable:
3r3444. public void WithNullPropagation (IEnumerable
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);
}
} 3r33450.
Note that the same pattern can be used for value types as well as reference types.
3r38080. Pattern matching in case
blocks.
In C # ? the functionality of the switch operator has been extended, so that samples can now be used in case clauses: 3r3486.
3r3444. public static int Count
(this IEnumerable
e)
{
switch (e) 3r3495. {
case ICollection
c: return c.Count;
case IReadOnlyCollection
c: return c.Count;
//Matches concurrent collections
case IProducerConsumerCollection
pc: return pc.Count;
//Matches if e is not null
case IEnumerable
_: return e.Count ();
//Default case is handled when e is null
default: return 0;
}
} 3r33450.
This example shows the first set of changes to the switch statement.
The switch statement can use any type of variable. 3r33473.
With the case clause you can specify a pattern 3r33473.
The order of sentences is important. The compiler will give an error if the previous sentence matches the base type, and the subsequent one is derived. 3r33473.
Non-standard sentences are implicitly checked for null **. In the example above, the last case clause is valid, since it matches only when the argument is not null. 3r33473.
3r33232. ** In the last case clause, another function added to C # 7 is an empty variable pattern. The special name _ tells the compiler that the variable is not needed. The type pattern in the case clause requires a pseudonym. But if you don't need it, you can use _. 3r33300.
The following fragment shows another feature of pattern matching based on the switch operator — the ability to use predicates:
3r3444. public static void FizzBuzz (object o)
{
switch (o)
{
case string 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;
}
} 3r33450.
This is a strange version of the task. FizzBuzz in which the object is processed, and not just a number.
A switch statement can include multiple case clauses with the same type. In this case, the compiler combines all type checks to avoid unnecessary computation:
3r3444. 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 of check and unboxing
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);
}
}
} 3r33450.
But two things need to be remembered:
1. The compiler combines only sequential type checking, and if you mix case clauses with different types, less quality code will be generated:
3r3444. switch (o)
{
//The generated code is less optimal:
//If o is int, then
checkout 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;
} 3r33450.
The compiler converts it like this:
if (o is int n && n == 1) return 1;
3r3444. if (o is string s && s == "") return 2;
if (o is int n2 && n2 == 2) return 3;
return -1; 3r33450.
2. The compiler does its best to avoid typical ordering problems.
3r3444. 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;
} 3r33450.
However, the compiler cannot determine that one predicate is stronger than another, and effectively replaces the following case clauses: 3r3486.
3r3444. switch (o)
{
case int n when n> 0: return 1;
//
case int n when n> 1: return 2;
} 3r33450.
3r38080. Briefly about pattern matching
The following patterns appeared in C # 7: a constant pattern, a pattern type, a sample variable, and a sample empty variable. 3r33473.
Patterns can be used in is expressions and in case blocks. 3r33473.
The implementation of the constant pattern in the is expression for value types is far from ideal in terms of performance. 3r33473.
Variable patterns are always the same, you have to be careful with them. 3r33473.
The switch statement can be used for a set of type checks with additional predicates in when clauses. 3r33473.
3r33475.
3r38080. Unity event in Moscow - Unity Moscow Meetup ???r3r3481.
October 1? Thursday, Unity Moscow Meetup 2018.1 will be held at VSHBI. This is the first meeting of Unity developers in Moscow this season. The theme of the first mitap will be AR /VR. You are waiting for interesting reports, communication with industry professionals, as well as a special demo zone from MSI.
Details
! function (e) {function t (t, n) {if (! (n in e)) {for (var r, a = e.document, i = a.scripts, o = i.length; o-- ;) if (-1! == i[o]src.indexOf (t)) {r = i[o]; break} if (! r) {r = a.createElement ("script"), r.type = "text /jаvascript", r.async =! ? r.defer =! ? r.src = t, r.charset = "UTF-8"; var d = function () {var e = a.getElementsByTagName ("script")[0]; e. ): d ()}}} t ("//mediator.mail.ru/script/2820404/"""_mediator") () (); 3r3494.
It may be interesting
weber
Author5-10-2018, 10:40
Publication DateProgramming / C# / .NET
Category- Comments: 2
- Views: 464
Lisa Zahner, an active investor and a recognized leader in San Francisco’s commercial real estate market, specializes in Multifamily and Investment Real Estate. Check Out: Multifamily Real Estate