Skip to content

Exhaustive switch expressions with enums in C#

Published:
Exhaustive switch expressions with enums in C#

A common scenario is doing something based on an enum value using a switch statement or switch expressions. For example:

public enum Animal { Dog, Jellyfish }

public bool IsPettable(Animal animal) => animal switch
{
    Animal.Dog => true,
    Animal.Jellyfish => false,
    _ => throw new ArgumentOutOfRangeException(nameof(animal), $"Unexpected animal: {animal}")
};

But let’s say someone adds a new enum value:

public enum Animal { Dog, Jellyfish, Cat }

They would have to remember to check all references where the enum is used and make changes to incorporate the value. If you have good tests they would catch any problems early, if not, you will find out at runtime.

We can do better by moving the feedback to compile time using an exhaustive switch expression. If after adding a new enum value the switch expression is no longer exhaustive we want to get a compiler error telling us so.

Removing the discard case will get us closer to our goal but it only produces a warning and does not stop the build (unless you treat errors as warnings):

CS8509	The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'Animal.Cat' is not covered.

It’s easy to fix by adding the following lines to our .editorconfig file:

# CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).
dotnet_diagnostic.CS8509.severity = error

Now we get a compiler error when the switch expression is not exhaustive!

Let’s complete the switch expression by adding the missing case:

public bool IsPettable(Animal animal) => animal switch
{
    Animal.Dog => true,
    Animal.Jellyfish => false,
    Animal.Cat => true
};

We’ve implemented all possible enum values but we’re still left with a 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 '(Animal)3' is not covered.

This too can be fixed by adding some lines to the .editorconfig file:

# CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
dotnet_diagnostic.CS8524.severity = none

Great! Now we get a compiler error when the switch expression is not exhaustive and no warnings when it is.

But what if an unnamed enum value is supplied? No worries, you still get an exception because the compiler automatically generates this case and throws an SwitchExpressionException for the unmatched value:

// This throws a SwitchExpressionException
var result = IsPettable((Animal)99);

Decompiled with SharpLab it looks (something) like this:

public bool IsPettable(Animal animal)
{
    switch (animal)
    {
        case Animal.Dog:
            return true;
        case Animal.Jellyfish:
            return false;
        case Animal.Cat:
            return true;
        default:
        {
            <PrivateImplementationDetails>.ThrowSwitchExpressionException(animal);
            bool result = default(bool);
            return result;
        }
    }
}

[CompilerGenerated]
internal sealed class <PrivateImplementationDetails>
{
    internal static void ThrowSwitchExpressionException(object unmatchedValue)
    {
        throw new SwitchExpressionException(unmatchedValue);
    }
}