有没有办法让 C# 编译器在开关 (enum_val) 缺少 case 语句时发出错误
本文关键字:case 缺少 val 语句 错误 出错 enum 编译器 开关 有没有 | 更新日期: 2023-09-27 17:55:44
我刚刚意识到我在枚举中的"必须处理"值列表中添加了一个值,但直到运行时我才捕获它。我知道 C# 编译器在类型的反射和内省方面非常强大,所以我想知道是否有办法强制 switch
/case
语句涵盖所有可能的enum
值?
例:
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"
我想要一个编译时解决方案。
您可以使用 SwitchEnumAnalyzer 获取此类编译器时警告。我只是在我的项目中使用它,它工作得很好。与任何 Roslyn 分析器一样,您可以选择通知级别 - 如果它应该只是警告或适当的错误。
没有编译时方法来实现这一目标。然而,非常简单的答案是有一个default
处理程序,它只是抛出一个异常,如"此选项未处理,嘘"。
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());
}
这正是我们需要在所有解决方案中进行测试的最佳示例。您必须编写一个可以枚举枚举的测试,并调用包含开关大小写的方法。使用此测试,每次编辑枚举时,您都会得到失败的测试,但忘记更新开关大小写。
[Test]
public void CheckThatAllEnumCoveredInSwitchCase()
{
foreach (var singleEnum in Enum.GetValues(typeof(YOURENUM)))
{
myclass.methodofswitchcase((YOURENUM)singleEnum);
}
}
C# 编译器没有内置此检查,但有一个 .Net 的代码协定检查器,它内置:https://blogs.msdn.microsoft.com/francesco/2014/09/12/how-to-use-cccheck-to-prove-no-case-is-forgotten/
该技术是使用代码协定断言来告诉检查器default
情况永远不可访问:
switch (c) {
case Colors.Red: break;
case Colors.Blue:
case Colors.Green: break;
default:
Contract.Assert(false); // Tell static checker this shouldn't happen
}
然后,如果检查器看到它是可访问的(因为其中一个枚举值未被处理),它将警告您:
warning : CodeContracts: This assert, always leading to an error, may be reachable.
Are you missing an enum case?
是的,现在可以
(Roslyn) 编译器现在内置了检查,因此无需再添加自定义分析器。添加到您的.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
如果缺少命名的枚举值大小写,则会收到编译时错误。
public enum E { Hi = 1, Bye = 2 }
与某处
return someValue switch
{
E.Hi => "Welcome!",
E.Bye => "Thanks for your visit!"
};
不会导致警告或编译器错误,因为同时处理命名值E.Hi
和E.Bye
。删除其中一个案例将导致编译器错误。
但是,请注意,枚举的基础类型(默认情况下)是一个int
,因此开关中实际可能的情况并未全部涵盖 - 在上面的示例中,(E)0
或其他整数值((E)3
等)可能会导致运行时错误。像上面这样的switch表达式会抛出一个System.Runtime.CompilerServices.SwitchExpressionException
,但switch语句不会抛出(所以以后可能会导致运行时错误)。当然,您可以为此添加检查(而不是使用默认情况,因为这会破坏我们获取编译器错误的尝试):
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!";
};
您可以使用在运行时检查的元方法,但至少检查整个开关。
https://github.com/faisalmansoor/MiscUtil/blob/master/EnumSwitch/EnumSwitch.cs
如果枚举大小,您可以在早期抛出异常,希望在对枚举进行更改时尽早提醒您:
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");
}
}
如果枚举中的项数发生更改,这将引发来自静态构造函数的异常。因此,开发人员知道他需要更新代码。您甚至可以从与生成一起运行的单元测试中执行此检查。
对于C++,您可以通过将编译器开关/we4061 添加到项目设置中来让编译器发出警告。 希望这有帮助。:)
http://msdn.microsoft.com/en-us/library/thxezb7y.aspx
http://msdn.microsoft.com/en-US/library/96f5t7fy(v=vs.80).aspx
实际上我错了;看起来你可以让编译器抛出错误。 只需使用/Wall/we4061 编译即可。 我自己还没有尝试过这个,但从阅读上面的 MSDN 页面来看,看起来这应该可以工作。
只需抛出(双关语无意)这个,您就可以用字典替换 Switch-Case (Func<int,int>
为例):
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");
}
}