Compiler error for exhaustive switch
Asked Answered
F

2

17

Why do I get a "not all code paths return a value", for VeryBoolToBool() in the following code?

public enum VeryBool { VeryTrue, VeryFalse };
public bool VeryBoolToBool(VeryBool veryBool)
{
    switch(veryBool)
    {
        case VeryBool.VeryTrue:
            return true;

        case VeryBool.VeryFalse:
            return false;

        // Un-commenting the unreachable(?) default clause will solve this
        // default:
        //    throw new HowTheHellDidIGetHereException();
    }
}

Can't the compiler see there are no other options for VeryBool?

Fox answered 24/12, 2013 at 10:17 Comment(1)
VeryBool val = (VeryBool)42; is perfectly legitimate. Your switch is not exhaustive.Pharmacopsychosis
P
19

Can't the compiler see there are no other options for VeryBool?

Nope, because there are. For example, I could call:

VeryBoolToBool((VeryBool) 5);

Enums in C# are not limited sets of values. They're effectively named numbers, with additional compile-time type safety in that there aren't implicit conversions between enums or between enums and numbers. (There are explicit conversions though.) The explicit conversions do not ensure that the value in question is one with a name, however.

Beyond that, switch in C# never checks whether all possible values of the type are explicitly listed. The end of a switch statement is always deemed "reachable" unless there's a default case (and all cases terminate). More precisely, from the end of section 8.7.2 of the C# 5 specification:

The end point of a switch statement is reachable if at least one of the following is true:

  • The switch statement contains a reachable break statement that exits the switch statement.
  • The switch statement is reachable, the switch expression is a non-constant value, and no default label is present.
  • The switch statement is reachable, the switch expression is a constant value that doesn’t match any case label, and no default label is present.
Postilion answered 24/12, 2013 at 10:20 Comment(5)
Hmm. I was wondering if this choice was aimed to ease the implementation of enums in .Net, or is there some benefit in doing stuff like Marc's VeryBool val = (VeryBool)42;?Fox
@bavaza: There are times when it's useful for enums to behave like this - but plenty of others where it's not. I wish there were a "strict enum" type in .NET, but for the moment we need to live with what we've got :(Postilion
The most common case where you would want this behavior is when your bool is being used as a bit field (for flags). If you used code like this, you could then use MyEnum foo = MyEnum.First | MyEnum.Second.Josh
This is true for Enums, but not for Disjoint Union Implemented via marker interface and inherited types. It would be nice if the compiler actually performed an exhaustive check, but I suppose with various dynamic assembly loading malarky you could always end up with an extra type at runtime. Oh C# how you teaseGarcon
This kind of crap makes you pray and hope for true sum types in C#Cheater
P
2

Since C# 8 you can use a switch expression to solve this:

public enum VeryBool { VeryTrue, VeryFalse };

public bool VeryBoolToBool(VeryBool veryBool)
{
    return veryBool switch
    {
        VeryBool.VeryTrue => true,
        VeryBool.VeryFalse => false
    };
}

Note that you will get a warning:

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 '(Fruit)2' is not covered.

But the warning can be suppressed or configured in your editor config (CS8524).

You can still pass an invalid enum value:

VeryBoolToBool((VeryBool) 5);

But this will throw a SwitchExpressionException.

Phylloquinone answered 5/3, 2023 at 19:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.