Can I force a non-exhaustive c# switch expression to cause a compile error?
Asked Answered
L

1

12

I like switch expressions for mapping enums to values - while not the most scalable solution, it's fast and fairly clean if the enum represents something modal (and isn't huge).

One often source of bugs is adding members to an enum - which often leaves new cases unhandled.

But I think these bugs could be nearly wiped out if we could have compile errors for a non-exhaustive switch, making the omissions easily visible and fixable. (The default case has to be omitted though, otherwise it's all moot)

Is this possible? I'm thinking of something like this:

public string GetTargetValue()
{
    return target switch
   {
       Target.A => "foo",
       Target.B => "bar",
       // Compile error if someone added Target.C, otherwise works fine
       // no default case - it would defeat the point
   };
}

P.S: I work primarily in Unity, but to my understanding newer versions of Unity use the Roslyn compiler (I don't use burst) so I assume that doesn't matter.

Lofton answered 2/7, 2021 at 15:24 Comment(9)
Personally, I always add a "default" part and then throw an exception. E.g. _ => throw new ArgumentOutOfRangeException(nameof(direction), $"Unexpected target value '{target}'."),Zischke
In addition the requirement to not have a "default case" seems to me might be an XY problem.Zischke
An enum can be any value of the base type. It don't need to be a named value. Like (Target)5 would work even is there no equivalent for 5 in the enum. So forcing all valid values in a switch seems extremely tedious.Physicalism
You can add CS8524 and CS8509 to the 'treat warnings as errors' section of your projectKumler
Warning CS8524 was introduced specifically to capture this very pattern (CS8509 is for cases not involving enums). Make that an error and you're there.Khachaturian
Ralf - The example was meant to be some generic mapping, I didn't mean casting the enum to it's integer value, which would definitely be redundant. I've edited the question to show a slightly less trivial mapping.Lofton
@JonathanLevin When the target variable has been set to target = (Target)5; before the shown method what should happen?Physicalism
@Physicalism The compiler inserts its own default case, which throws. See hereCoxa
@Coxa That's cool.Physicalism
C
15

Yes you can.

This case raises the warning CS8509, as you can see here. To turn this into an error, add the following to your .editorconfig:

dotnet_diagnostic.CS8509.severity = error

You might also want to ignore CS8524, which happens if you don't have a default case, and CS8509 isn't being raised (so even if you're covering all possible values), see here. In this case, the compiler will insert a default case with throw new SwitchExpressionException(target):

dotnet_diagnostic.CS8524.severity = none

I suspect you probably need to be building with the .NET 5 SDK or higher for this warning to be generated.

Coxa answered 2/7, 2021 at 15:30 Comment(3)
Unfortunately it seems this warning is generated as long as the default case is missing, which would mean it would fail compilation no matter the enum values.Lofton
@JonathanLevin No? If you include all cases, you instead get CS8524. You can choose to ignore that one, if you wish.Coxa
Ah! I checked your link and saw similar warnings in each case, missed that they had different codes. Looks like this should work, thanks :)Lofton

© 2022 - 2024 — McMap. All rights reserved.