C#7: Pattern Matching

So far in this series on C#7, we have looked at some nice new things, including out variables, new expression-bodied members, throw expressions, and binary numeric literals. These are all great little additions, but this week, we get to a truly cool and long-awaited feature; pattern matching. But, before we get into that, here is the usual summary of what I am covering.

Pattern Matching

I will admit that I was a tad confused as to why this is called "pattern matching". I read the Wikipedia article1. I expect I would understand this more if I were a Computer Science major instead of Computer Systems Engineering2. According to Wikipedia:

In computer science, pattern matching is the act of checking a given sequence of tokens for the presence of the constituents of some pattern.

The What's New in C#7 documentation explains:

Pattern matching is a feature that allows you to implement method dispatch on properties other than the type of an object.

While I now understand the academic concept of pattern matching, I think we may regret using the term to specifically reference the features that are under its umbrella for C#7. I would have preferred it if these features had been introduced using already widely understood and consistent nomenclature. I think the official documentation agrees with me, since it almost immediately splits the feature into two pieces; is expressions and switch expressions. However, I am going to draw the line that divides the parts of pattern matching in a different place and call the two parts cast-conditional variables and case filters, because that fits better with my understanding of what they do.

Cast-conditional Variables

Many of us know the following code:

var x = y as MyType;
if (x != null)
{
   //Do the awesome!
}

This is the efficient way of verifying a value is a given type and then consuming it as that type since we cast just once3. However, with the new cast-conditional feature, we can condense this down to:

if (y is MyType x)
{
    //Do the awesome!
}

The cast and the check of its success have been condensed into a single, easily understood statement.  We can use this cast-conditional variable syntax in any place where we might otherwise but a boolean expression:

// Example 1: Calculating a boolean
bool isDoubleNan = value is double y && Double.IsNaN(y);

// Example 2: Ternary operator
string filePath = size is int x && x > 10 ? "C:\Archive" : "C:\TinyFiles";

This feels like a great improvement to me and fits really well with the similar out variable feature, however it is only a new way of writing something we could already do. When extended to the cases in switch statements (albeit with a slightly different syntax), this gives us a brand new ability; switching based on variable type:

switch (myInterface)
{
case MyTypeA a:
    // Do something because we know this is of type, MyTypeA
    break;

case MyTypeB b:
    // Do something because we know this is of type, MyTypeB
    break;
}

Now you can iterate over that array of object values and have a nice, succinct way of processing the contents by type. Not only that, but with case filters, you can craft even finer conditions for your switches.

Case Filters

C#6 introduced the when keyword as a modifier to catch expressions so that we could finally utilize exception filters from C#. Now, the when keyword gets a similar job in switch statements as a modifier to case statements. Like in exception filters, the when expression is a condition that must be met for the case to be a match. For example, we could create an IsNumber method and use when to filter cases like Infinity and NaN:

bool IsNumber(object value)
{
    switch (value)
    {
    case int x:
    case float y when !float.IsNaN(y) && !float.IsInfinity(y):
    case double z when !double.IsNaN(z) && !double.IsInfinity(z):
        return true;

    default:
        return false;
    }
}

Prior to C#7, this code would look something like this:

bool IsNumberOld(object value)
{
    int? x = value as int?;
    if (x.HasValue) return true;
    
    float? y = value as float?;
    if (y.HasValue && !float.IsNaN(y.Value) && !float.IsInfinity(y.Value)) return true;

    double? z = value as double?;
    if (z.HasValue && !double.IsNaN(z.Value) && !double.IsInfinity(z.Value)) return true;

    return false;
}

Not only was there more typing before C#7, but I think the code was more repetitive and harder to scan. This may be a convoluted example, but I hope it illustrates how valuable this new language syntax can be.

In Conclusion

Naming disagreements aside, the new pattern matching in C#7 is powerful. With that power comes responsibility; the responsibility to use it wisely and to call out others who do not, for there is surely great room for abuse with this feature. I envisage frequent and appropriate use of cast-conditional variables in if statements since the scenarios to which that caters are widespread. However, the filtering added to switch statements brings something entirely new and so, I do not see it being as widely adopted not as appropriately used; time will tell.

Overall, I love this addition to C#7 even though I do not like the name. What do you think? Does "pattern matching" make sense or should it be something like "cast-conditional variables" and "case filters" instead? Will you use this feature a lot? When might you find pattern matching useful? Sound off in the comments and discuss.

 

  1. that's a lie; I read parts of it until I came to the conclusion that it was not helping []
  2. Is this a theory versus practice issue again? I face those often in this field []
  3. Using the is condition and then casting inside the if statement would cause two casts; one for the is and another for the cast []