Page 24 - MSDN Magazine, May 2019
P. 24
we find new ways to express a constraint on the properties of a type? How can we make the expression of block patterns more intuitive and readable? In C# 8.0 the language takes another step forward to introduce a way to work with patterns that should be very familiar to those who’ve worked in languages like Kotlin. These are all won- derful additions that make the code readable and maintainable.
First, we now have an option to use something called a switch expression, instead of the traditional switch statement that devel- opers have been using since C# 1.0. Here’s an example of a switch expression in C# 8.0:
var whatFruit = fruit switch { Apple _ => "This is an apple", _ => "This is not an apple"
};
As you can see, instead of having to write case and break for each different match, I simply use a pattern and an expression. When I match for a fruit, the underscore (_) means that I don’t care about the actual fruit that I matched on. In fact, it doesn’t have to be an initialized type of fruit. The underscore will match on null, as well. Think of this as simply matching on the specific type. When I found this apple, I returned a string using an expression—much like the expression-bodied members that were introduced in C# 6.0.
This is about more than just saving characters. Imagine the possibilities here. For example, I could now introduce an expression- bodied member that includes one of these switch expressions, which also leverages the power of pattern matching, like so:
public Fruit Fruit { get; set; } public string WhatFruit => Fruit switch {
Apple _ => "This is an apple",
_ => "This is not an apple" };
This can get really interesting and powerful. The following code shows how you would perform this pattern match in the traditional manner. Have a look and decide which one you would prefer:
public string WhatFruit
{ get {
I’ve already covered the fact that switch expressions can cut down the amount of code you write, as well as make that code more readable. This is true also when adding constraints to your types. The changes to pattern matching in C# 8.0 really stand out when you look at the combination of tuples, deconstruction and what’s known as recursive patterns.
As you can see, instead of having to write case and break for each different match, I simply use a pattern and an expression.
Expressing Patterns
A recursive pattern is when the output of one pattern-match expres- sion becomes the input of another pattern-match expression. This means deconstructing the object and looking at how the type, its properties, their types, and so forth are all expressed, and then apply- ing the match to all of these. It sounds complicated, but really it’s not.
Let’s look at a different type and its structure. In Figure 2 you’ll see a rectangle that inherits from Shape. The shape is just an abstract class that introduces the property point, a way for me to get the shape onto a surface so I know where it’s supposed to go.
You might wonder what the Deconstruct method in Figure 2 is all about. It allows me to “extract” the values of an instance into new variables outside of the class. It’s commonly used together with pattern matching and tuples, as you’ll discover in a moment.
So, I essentially have three new ways to express a pattern in C# 8.0, all of which have a specific use case. They are:
• Positional pattern • Property pattern • Tuple pattern
Don’t worry, if you prefer the normal switch syntax, you can use these pattern-matching improvements with that, as well! These changes and additions to the language in terms of pattern match- ing are commonly referred to as recursive patterns.
The positional pattern leverages the deconstruct method that you have on your class. You can express a pattern that matches
Figure 2 Example of Deconstruct
if(Fruit is Apple) {
return "This is an apple"; }
return "This is not an apple"; }
}
Obviously, this is a very simple scenario. Imagine when I intro- duce constraints, multiple types to match against, and then use the casted type within the condition context. Sold on the idea yet? I thought so!
While this is a welcome addition to the language, please resist the urge to use switch expressions for every if/else if/else condition. For an example of what not to do, check out the following code:
bool? visible = false;
var visibility = visible switch {
true => "Visible", false => "Hidden", null, _ => "Blink"
};
This code indicates that you could have four cases for a nullable Boolean, which of course you can’t. Just be mindful about how you use switch expressions and don’t abuse the syntax, exactly as you would with any other language feature.
abstract class Shape {
public Point Point { get; set; } }
class Rectangle : Shape {
public int Width { get; set; } public int Height { get; set; }
public void Deconstruct(out int width, out int height, out Point point) {
width = Width; height = Height; point = Point;
} }
18 msdn magazine
C#