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 []

C#6: Exception Filters

The for the last six1 releases the C# compiler has been keeping part of the .NET Framework secret from us2; exception filters. It turns out that the .NET Framework has supported exception filters since the very beginning, there was just no way to express them using C# until now.

C#6 adds the when keyword for use in try/catch blocks to specify exception filters. An exception filter is a predicate method that takes the thrown exception and returns true when the exception should be caught or false when it should not. If the filter says the exception should not be caught, the underlying system can continue to throw it.

This allows us to reduce the complexity in our code as we can put multiple catch statements with different filtering rules in the same try/catch block. This gives a switch-style approach to exception handling that is supported at the lowest level, reducing the need to rethrow exceptions (or to remember the difference between throw and throw exceptionVar;)3.

Here is a try/catch block showing an example of exception filtering:

Func<ArgumentException,string,bool> filterParameterName = (e,s) => e.ParamName == s;
try
{
    CallSomething("param1", "param2", "param3", "param4");
}
catch (ArgumentException ex) when (ex.ParamName == "param1")
{
    Console.WriteLine("Filtered: param1");
}
catch (ArgumentException ex) when (filterParameterName(ex, "param2"))
{
    Console.WriteLine("Filtered: param2");
}
catch (ArgumentException ex)
{
    Console.WriteLine($"Unfiltered: {ex.ParamName}");
}

Before I continue, I must state that this is a completely contrived example for demonstrable purposes; your filters would probably act on more than just the value of a string, the two filters shown would use the same code, and the handling would involve different things in each catch4.

Now, some things to note. First, the parentheses around the when condition are mandatory; you don't need to remember this as the compiler and syntax highlighting will remind you. Second, the content of the when condition must evaluate to bool; you cannot specify a lambda expression here. I am certain most of you already assumed that, but for some reason, I felt like that should be possible. However,  when is akin to if or while, so it makes sense that a lambda expression would not work.

The example above provides three different catch blocks for the exact same exception type, ArgumentException. Each filter is evaluated in the order specified, so, if CallSomething() threw an ArgumentException with ParamName set to param2, the when condition on the first catch would reject it, but the second filter would catch it and handle accordingly. A ParamName value filtered out of the first two catch blocks would fall into the last.

In conclusion

Exception filtering is a useful and simple concept that should help to make exception handling easier to write. While some kind of filtering could be achieved before using conditions and throw inside of catch blocks, this language support now means that exception handlers (the content of catch blocks) have a single responsibility and the catch statements themselves are entirely responsible for declaring what must be caught. It also means that the exception handling within the .NET framework can be entirely responsible for routing exceptions in C#-implemented applications.

Exception filters have been supported by VB.NET and .NET-supporting variants of C++ since the versions released alongside .NET Framework 1.1; now, as of C#6, they are supported by C# too.

  1. 1.0, 1.2, 2.0, 3.0, 4.0, 5.0 []
  2. Actually, it's been keeping several, but we can't have everything []
  3. The first rethrows the original exception with the stack unchanged, the second throws a new exception and resets the stack []
  4. otherwise, why filter? []

C#6: Support for .NET Framework 2.0 to 4.5

A colleague of mine, Eric Charnesky, asked me if C#6 language features would work in .NET Framework versions other than 4.6. I was pretty confident that the features were almost all1 just syntactical seasoning, I thought I would find out.

The TL;DR is yes, C#6 features will work when compiled against .NET 2.0 and above, with a few caveats.

  1. Async/await requires additional classes to be defined since the Task Parallel Library, `IAwaitable` and other types were not part of .NET 2.0.
  2. The magic parts of string interpolation need some types to be defined (thanks to Thomas Levesque for catching this oversight).
  3. Extension methods need the declaration of `System.Runtime.CompilerServices.ExtensionAttribute` so that the compiler can mark static methods as extension methods.

Rather than just try .NET 4.5, I decided to go all the way back to .NET 2.0 and see if I could write and execute a console application that used all the following C#6 features:

The code I used is not really important, though I have included it at the end of this post if you want to see what I did. The only mild stumbling block was the lack of obvious extension method support in .NET 2.0. However, extension methods are a language-only feature; all that is needed to make it work is an attribute that the compiler can use to mark methods as extension methods. Since .NET 2.0 doesn't have this attribute, I added it myself.

Exclusions

You might have noticed that I did not verify a couple of things. First, I left out the use of `await` in `try`/`catch` blocks. This is because .NET 2.0 does not include the BCL classes that the compiler expects when generating the state machines that drive async code. You might be able to find a third-party implementation that would add support, but my brief3 search was fruitless. That said, this feature will definitely work in .NET 4.5 as it is an update to how the compiler builds the code.

Second, I did not intentionally test the improved overload resolution. The improvements mostly seem to relate to resolution involving overloads that take method groups and nullable types. Unfortunately, in .NET 2.0 there was were no `Func` delegate types nor nullable value types (UPDATE: Nullable types totally existed in .NET 2.0 and C#2; thanks to Thomas Levesque for pointing out my strange oversight here – I blame the water), making it difficult to craft an example that would demonstrate this improvement. However, overload resolution affects how the compiler selects which method to use for a particular call site. Once the compiler has made the selection, it is fixed within the compiled output and as such, the version of the .NET framework has no bearing on whether the resolution is correct4.

Did it work?

With the test code written, I compiled and ran it. A console window flickered and Visual Studio returned. The code had run but I had forgotten to put anything in there that would give me chance to read the output. So, I dropped a breakpoint in at the end, and then ran it under the debugger. As I had suspected it might, everything worked.

Testing under .NET 2.0 runtime on Windows XP
Testing under .NET 2.0 runtime on Windows XP

Then I realised I was still executing it on a machine that had .NET 4.6 and therefore the .NET 4 runtime; would it still work under the .NET 2 runtime? So, I cracked open5 a Windows XP virtual machine from modern.ie and ran it again. It didn't work, because Windows XP did not come with .NET 2.0 installed (it wasn't even included in any of the service packs), so I installed it and tried once more. As I had suspected it might, everything worked.

In conclusion

If you find yourself still working with old versions of the .NET framework or the .NET runtime, you can still use and benefit from most features of C#6. I hope my small effort here is helpful. If you have anything to add, please comment.

Here Lies The Example Code6

using System;
using System.Collections.Generic;
using static System.Math;

namespace System.Runtime.CompilerServices
{
    [AttributeUsage( AttributeTargets.Method )]
    public class ExtensionAttribute : Attribute
    {
    }
}

namespace CSharp6andNET2
{
    internal class Program
    {
        public delegate bool Filter( ArgumentException ex, string argName );

        public bool DoFilter( Filter test, ArgumentException ex, string argName )
        {
            return test( ex, argName );
        }

        private static void Main( string[] args )
        {
            Filter x = ( ArgumentException ex, string argName ) => ex.ParamName == argName;
            for ( int count = 0; count < 2; count++ )
            {
                try
                {
                    ExceptionFilterTest( count == 0 );
                }
                catch ( ArgumentException e ) when (x( e, "argumentName" ))
                {
                    Console.WriteLine( "Logged filtered exception" );

                    var test = new TestClass();
                    Console.WriteLine(
                        $"{nameof( test.Count )}: {test.Count}, {nameof( test.Count2 )}: {test.Count2}{nameof( test.Count3 )}: {test.Count3}, {nameof( test.Count4 )}: {test.Count4( 1 )}" );

                    var list = new List<int>
                    {
                        "9",
                        "25",
                        "36",
                        16,
                        4,
                        64
                    };

                    Console.WriteLine( "List" );
                    foreach ( var n in list )
                    {
                        Console.WriteLine( Sqrt( n ) );
                    }

                    var dictionary = new Dictionary<int, string>
                    {
                        [ 4 ] = "Test",
                        [ 2 ] = null,
                        [ 45 ] = null,
                        [ 34 ] = null,
                        [ 200 ] = null,
                        [ 16 ] = "Another test"
                    };

                    foreach ( var k in dictionary )
                    {
                        Console.WriteLine( $"{k.Key}: {k.Value?.Substring( 0, 3 )}" );
                    }
                }
                catch ( Exception )
                {
                    Console.WriteLine( "Logged exception" );
                }
            }
        }

        private static void ExceptionFilterTest( bool filterable )
        {
            if ( filterable ) throw new ArgumentException( "Exception", "argumentName" );

            throw new Exception( "Exception" );
        }
    }

    internal static class Extensions
    {
        public static void Add( this List<int> list, string value )
        {
            list.Add( Int32.Parse( value ) );
        }
    }

    internal class TestClass
    {
        public int Count { get; } = 6;
        public int Count2 { get; set; } = 6;
        public int Count3 => 6;
        public int Count4( int x ) => x + 6;
    }
}

 

  1. Async/await requires the TPL classes in the BCL, extension methods need the ExtensionAttribute, and exception filters require some runtime support []
  2. The Elvis's []
  3. very brief []
  4. I realise many of the C#6 features could be left untested for similar reasons since almost all are compiler changes that do not need framework support, but testing it rather than assuming it is kind of the point []
  5. Waited an hour for the IE XP virtual machine to download and then get it running []
  6. Demonstrable purposes only; if you take this into production, on your head be it []