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");
}
}
精彩评论