Using the pattern capabilities in C# can make code more readable and easier to understand but can come with some pitfalls that could catch you off guard if you are not careful.
Pattern power
Patterns can be especially useful when used with Enumerations. For example, given the following enum:
1enum OrderStatus
2{
3 Placed,
4 Processing,
5 Shipped,
6 Delivered
7}
8
9OrderStatus orderStatus = OrderStatus.Placed;
if you wanted to check if the order status was one of two potential values without using patterns you would typically do this:
1if (orderStatus == OrderStatus.Placed || orderStatus == OrderStatus.Processing)
2{
3 // do something
4}
but with a pattern, you don’t need to repeat the variable that you are checking:
1if (orderStatus is OrderStatus.Placed or OrderStatus.Processing)
2{
3 // do something
4}
This expression is shorter in length and follows a more natural way of reading. But what if you wanted to check if the order status was not one of two different values?
With great power…
On the surface, you would right it like this, right?:
1if (orderStatus is not OrderStatus.Shipped or OrderStatus.Delivered)
2{
3 // do something
4}
This compiles and appears to be correct. Wouldn’t you naturally assume that you
would enter the body of the if
block when the order status is not Shipped or
Delivered? If you run this code with orderStatus = OrderStatus.Shipped
you
will skip the body of the if statement… as expected. However, if you set
orderStatus = OrderStatus.Shipped
you will enter the body of the if
block!! But why?
… comes great responsibility
The reason is that the C# compiler is actually interpreting the if
expression as
if it had been written like this (which can be confirmed with unit tests):
1if (orderStatus is not OrderStatus.Shipped || orderStatus is OrderStatus.Delivered)
2{
3 // do something
4}
now that you understand that, your first few thoughts might be to try and mess
with the combinations of the is not
and and
/or
keywords but the only
syntax that is going to be valid is to group the conditions being checked inside
of parentheses as follows:
1if (orderStatus is not (OrderStatus.Shipped or OrderStatus.Delivered))
2{
3 // do something
4}
Once it is written this way, you will only enter the body of the if
block when
the order status is not equal to OrderStatus.Shipped
or
OrderStatus.Delivered
.
Wrap up
This is something that Visual Studio will not currently alert you to unless you have the JetBrains ReSharper plugin installed. Fortunately, I was writing code like this using JetBrains Rider which flagged this issue with the warning of
The pattern is redundant, it does not produce any runtime checks
which led me to the description and solution for this problem in the JetBrains Rider documentation.