4 min read

Switch expressions in C#

Switch expressions in C#
Photo by Jaye Haych / Unsplash

A recent introduction to the C# language is pattern matching. It was added in C# 7.0, which came out in March 2017, along with Visual Studio 2017 and .NET Framework 4.7. So it's a pretty well-established concept. But one I haven't used very much at all.

The feature I want to write about today was introduced with C# 8.0, in September 2019 (around the time that .NET Core 3.0 came out). It is pattern matching using switch expressions.

A quick intro if you're not a coder. A lot of programming is about making decisions. If something is true, do this, otherwise, do that. Sometimes it's not a binary choice. You might want to do different things depending on 3 or 4 or 10 different cases. This is where switch comes in. You specify each condition you're testing for, and what should happen if that condition is true. The usual way is a switch statement but there's a new way of achieving a similar thing, calls a switch expression.

I recently found an occasion to a switch expression in a recent piece of work. I am working on the API that serves a business web application. There is a backend API that could return an error as part of its regular operation, and that error is of an error type enum.

My API needs to return that error in the way the frontend will understand. I need to translate it.

This is what comes from the backend API:

enum ErrorType
{
    Good,
    Bad,
    TooBig
}

class BackendApiResult
{
    ErrorType ErrorType { get; set; }
    string Message { get; set; }
}

My API's response needs to look like this:

{ 
    "result": "GOOD" | "BAD" | "TOO_BIG",
    "maxSize": 1000 // only present if result is TOO_BIG
}

One way to do this would be:

switch (apiResponse.ErrorType)
{
    case ErrorType.Good:
        return new { Result = "GOOD" };

    case ErrorType.Bad:
        return new { Result = "BAD" };

    case ErrorType.TooBig:
        return new { Result = "TOO_BIG", MaxSize = GetMaxSize(apiResponse) };

    default:
        return new { Result = apiResponse.Message };
}

It's quite verbose. But to use pattern matching:

return apiResponse.ErrorType switch
{
    ErrorType.Good => new { Result = "GOOD" },
    ErrorType.Bad => new { Result = "BAD" },
    ErrorType.TooBig => new { Result = "TOO_BIG", MaxSize = GetMaxSize(apiResponse) },
    _ => new { Result = apiResponse.Message },
};

Less wordy, possibly easier to follow, assuming you know the new syntax.

It should be mostly self-explanatory. The _ is the discard pattern, a catch-all, similar to the default in a switch statement.

What is different from a switch statement is that the compiler will warn you if you don't cover all the cases. If I hadn't included the discard pattern at the end, I'd have received

Warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(ErrorType)3' is not covered.

The compiler knows that other values could be provided in the ErrorType property, and lets me know. To be fair, in this example, if I didn't have the default case, the compiler would tell me that not all code paths return a value, but it's not as explicit as to what was missing.

What does it look like behind the scenes? I loaded the code into dotPeek, and looked at the low-level C# view of the IL. They are very similar.

The switch statement implementation looks like this:

[NullableContext(1)]
[CompilerGenerated]
internal static object \u003C\u003CMain\u003E\u0024\u003Eg__GetThingTheOldWay\u007C0_1(
  BackendApiResult apiResponse)
  {
    switch (apiResponse.ErrorType)
    {
      case ErrorType.Good:
        return (object) new \u003C\u003Ef__AnonymousType0<string>("GOOD");
      case ErrorType.Bad:
        return (object) new \u003C\u003Ef__AnonymousType0<string>("BAD");
      case ErrorType.TooBig:
        return (object) new \u003C\u003Ef__AnonymousType1<string, int>("TOO_BIG", Program.\u003C\u003CMain\u003E\u0024\u003Eg__GetMaxSize\u007C0_2(apiResponse));
      default:
        return (object) new \u003C\u003Ef__AnonymousType0<string>(apiResponse.Message);
    }
}

The pattern matching implementation looks like:

[NullableContext(1)]
[CompilerGenerated]
internal static object u003C\u003CMain\u003E\u0024\u003Eg__GetThing\u007C0_0(
  BackendApiResult apiResponse)
{
  ErrorType errorType = apiResponse.ErrorType;
  if (true)
    ;
  object thing00;
  switch (errorType)
  {
    case ErrorType.Good:
      thing00 = (object) new \u003C\u003Ef__AnonymousType0<string>("GOOD");
      break;
    case ErrorType.Bad:
      thing00 = (object) new \u003C\u003Ef__AnonymousType0<string>("BAD");
      break;
    case ErrorType.TooBig:
      thing00 = (object) new \u003C\u003Ef__AnonymousType1<string, int>("TOO_BIG", Program.\u003C\u003CMain\u003E\u0024\u003Eg__GetMaxSize\u007C0_2(apiResponse));
      break;
    default:
      thing00 = (object) new \u003C\u003Ef__AnonymousType0<string>(apiResponse.Message);
      break;
  }
  if (true)
    ;
  return thing00;
}

Apart from assigning the property to a variable at the start (which in some languages is more efficient because the expression will be evaluated for each case - but this isn't how it works in .NET) and saving the result in a variable and returning it at the end, they are the same.

Which is faster? My hypothesis is that the one that the switch statement will be. After all, it's doing less work.

Box and whisker of time taken for switch expression vs switch statement

Looking at the graph above, you can see it's slightly faster to use the switch statement. My test involved calling a switch statement/expression 100,000,000 times. I ran it 1,000 times for the expression and 1,000 for the statement and recorded the time tacken (in ticks). It test wasn't very scientific and the computer was doing other things while it was running!

Method Average time Standard deviation
Expression 4071 1149
Statement 3870 1138

So the statement appears to be about 5% faster. Again, the test isn't that scientific so I wouldn't base any decision on whether to use an expression or statement on this alone!

What do you think? Do you use switch expressions? Let me know in the comments!