Why do we need it?
Remember boring polymorphism examples where Circle
and Square
are derived from Shape
and
each one override Draw
method? This works well if
you are dealing with classes. What if you just have simple values
and no inheritance is possible?
New C#7 extended existing is
and switch
language constructs to check if item is of the certain "shape". Now
you can execute different code according to "shape" of the tested
item (similar to polymorphism). In the past is
was
testing only against a type and case
was matching
only constant values. Today both can test against pattern containing
value types as well as reference types.
is example
If pattern is matched value can be extracted to a variable.
This is the part I love. Example below tests each list
item with is
keyword against int
or IEnumerable
.
If match is confirmed item
is extracted to a new
variable (value
or list)
. As a result
we will add up all numbers from complex list containing not only
integers but lists too.
We can write something similar without pattern matching but code above is more concise and more readable.
Prior to the version 7 is
expression was testing only against type. Now we can test against constant or even structure. On the right side of is
now we can have a pattern (will introduce more complex patterns later). Same goes for case
inside of switch
.
switch example
For the next example lets switch to switch
(pun intended). Same as is
, switch
now supports pattern matching. Let's improve our code and test against constant 0
, null
value and also introduce default
case. Note that default
case will be executed last regardless of where is located in the switch
statement. To avoid confusion, put it at the end. All other cases are executed in textual order.
We are switching on any type and not only primitive types as before.
Breaking changes
- You can't fall thru cases! Each case must end with
break
,return
orgoto
. - Order is important!
case 0
must be beforecase int
or it will never be matched (zero case is subset of int case).
Good news is that compiler will generate error for any of above.
when condition
Pattern can be enriched with conditions. Let's introduce matching to a structure (value type) and also apply when
clause. Our funny list now contains structure Person
. Person age will be included in sum only if person is 18 or older. Also we will introduce nullList
to show how case
IEnumerable<object>
list
will not match this null
.
Again, order is important! case Person p when p.Age < 18
must be before case Person p
since it is more restrictive! In example above we merged two cases (zero and person < 18) but we are not using any variable inside that block. We are just doing break
. Keep in mind that using p
inside first block would be illegal since when case 0
is executed p
is unassigned! No worries, compiler will detect that.
case null
Matching pattern guarantees a non-null value. In example above, compiler didn't complain that case null
is unreachable. All cases are checked against null
and they will not match if variable is null
. Therefore we need to handle null
separately.
var pattern
var
pattern will match everything including null
! Proof that it will match everything is a compiler error if you uncomment case int
or case null
in example below. Since matching with var
is before those two cases they are unreachable.
A million dollar question is why compiler didn't complain about default
case?
Inconsistent scope
Defining variables in if-else
has a bit of inconsistency.
Variable text
is defined in if
statement and has scope same as if
statement itself - entire surrounding function.
If we try to use it after if-else
statement compiler will complain that variable is unassigned. On the other side if we try to use number
variable from else
statement in the same way error will be different. Variable is out of scope. If we understand that else-if
is
actually another if
inside else
block all makes sense.
Conclusion
Pattern matching is one more step forward in making C# more concise and readable. At first having four items in if
expression was strange. Now looks so normal.