开发者

Is there a way to get the C# compiler to emit an error if a switch(enum_val) is missing a case statement?

开发者 https://www.devze.com 2023-03-04 01:15 出处:网络
I just realized I added a value to the list of \"must-handle\" values in my enum, bu开发者_如何学Pythont I didn\'t catch it until runtime. I know the C# compiler is really powerful when it comes to re

I just realized I added a value to the list of "must-handle" values in my enum, bu开发者_如何学Pythont I didn't catch it until runtime. I know the C# compiler is really powerful when it comes to reflection and introspection of types, so I was wondering if there was a way to force a switch/case statement to cover all possible enum values?

Example:

enum Colors
{
   Red,
   Blue,
   Green,
   Yellow
};

Colors c = ...;

switch (c)
{
   case Colors.Red:  // No error, Red is a Color
      break;
   case Colors.Blue:
   case Colors.Green:  // No error, Blue and Green handled as well
      break;
}  // whoops! "error: 'Colors.Yellow' unhandled"
   // or even, "error: no 'default' and 'Colors.Yellow' unhandled"

I want a compile-time solution.


You can use SwitchEnumAnalyzer to get compiler-time warnings of this kind. I just used it in my project and it works nicely. As in any Roslyn analyzer, you can choose level of notification - if it should be just warning, or a proper error.


No there's no compile-time way to make that happen. However the very VERY simple answer is to have a default handler which simply throws an exception along the lines of, "this option wasn't handled, boo".

switch (c)
{
    case Colors.Red:  // no error, Red is a Color
        break;
    case Colors.Blue:
    case Colors.Green:  // no error, Blue and Green handled as well
        break;
    default:
        throw new Exception("Unhandled option: " + c.ToString());
}


This is exactly best example why we need Testing in all solutions. You have to write a test that can enumerate your Enum and call that method which contains the switch case. With this test you will get a failed test every time you edit the Enum but forget to update the switch case.

    [Test]
    public void CheckThatAllEnumCoveredInSwitchCase()
    {

        foreach (var singleEnum in Enum.GetValues(typeof(YOURENUM)))
        {
                myclass.methodofswitchcase((YOURENUM)singleEnum);
        }

    }


You could throw an exception early on if the enum size, hopefully alerting you very early on when a change to the enum has been made:

enum MyEnum {A, B};

class TestEnum
{
    // Static constructor
    static TestEnum()
    {
        // Check if this code needs updating as the enum has changed
        if (Enum.GetNames(typeof(MyEnum)).Length != 2)
        {
            // If this fails update myFunction and the number above
            throw new Exception("Internal error - code inconsistency");
        }
    }

    // My function that switches on the enum
    string myFunction(MyEnum myEnum)
    {
        switch (myEnum)
        {
            case MyEnum.A: return "A";
            case MyEnum.B: return "B";
        }
        throw new Exception("Internal error - missing case");
    }
}

This will throw an exception from the static constructor if the number of items in the enum is changed. So the developer knows he needs to update the code. You could even do this check from a unit test that you run with your build.


The C# compiler doesn't have this check built-in, but there's a code contracts checker for .Net that does: https://blogs.msdn.microsoft.com/francesco/2014/09/12/how-to-use-cccheck-to-prove-no-case-is-forgotten/

The technique is to use a code contract assertion to tell the checker that the default case should never be reachable:

switch (c) {
  case Colors.Red: break;
  case Colors.Blue:
  case Colors.Green: break;
  default:
    Contract.Assert(false); // Tell static checker this shouldn't happen
}

Then, if the checker sees that it is reachable (because one of the enum values is not handled), it will warn you:

warning : CodeContracts: This assert, always leading to an error, may be reachable.
Are you missing an enum case?


Yes, it is possible now

The (Roslyn) compiler now has checks built-in, so you don't need to add custom analyzers anymore. Add to your .editorconfig:

dotnet_diagnostic.CS8509.severity=error # missing switch case for named enum values
dotnet_diagnostic.CS8524.severity=none # missing switch case for unnamed enum values

You will get a compile time error if a named enum value case is missing.

public enum E { Hi = 1, Bye = 2 }

with somewhere

return someValue switch
{
    E.Hi => "Welcome!",
    E.Bye => "Thanks for your visit!"
};

will not result in a warning or compiler error, because both named values E.Hi and E.Bye are handled. Removing one of the cases will result in a compiler error.

However, be mindful about the fact that the underlying type of an enum is (by default) an int and that therefore the actual possible cases in the switch are not all covered - in the above example, (E)0 or other integer values ((E)3, etc) may cause runtime errors. A switch expression like above will throw a System.Runtime.CompilerServices.SwitchExpressionException, but a switch statement will not throw (so may result in a runtime error later). Of course you can add a check for this (instead of using a default case, because that would ruin our attempt to get compiler errors):

if (!Enum.IsDefined<E>(someValue))
{
    throw new Exception($"Enum E does not have a value '{someValue}'");
}
switch (someValue)
{
    case E.Hi:
        DoSomeThing();
        return "Welcome!";
    case E.Bye:
        DoSomeThingElse();
        return "Thanks for your visit!";
};


You can use a meta-method that checks at runtime, but at least checks the whole switch.

https://github.com/faisalmansoor/MiscUtil/blob/master/EnumSwitch/EnumSwitch.cs


For C++ you can get the compiler to issue a warning by adding the compiler switch /we4061 to your project settings. Hope this helps. :)

http://msdn.microsoft.com/en-us/library/thxezb7y.aspx

http://msdn.microsoft.com/en-us/library/96f5t7fy(v=vs.80).aspx

Actually I was mistaken about this; it appears you can have the compiler throw an error. Just compile with /Wall /we4061. I haven't tried this myself, but from reading the above MSDN pages, it looks like this should work.


Just by throwing (pun unintended) this out, you can replace the Switch-Case with a dictionary (Func<int,int> as an example):

Dictionary<Colors,Func<int,int>> d = new Dictionary<Colors, Func<int, int>>();
d.Add(Colors.Red, (x) => x+1);
d.Add(Colors.Blue, (x) => x+1);
d.Add(Colors.Green, (x) => x+1);
foreach (Colors color in Enum.GetValues(typeof(Colors)))
{
    if (!d.ContainsKey(color))
    {
        throw new Exception("Poor color " + color + " ignored");
    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消