繁体   English   中英

如果 switch(enum_val) 缺少 case 语句,有没有办法让 C# 编译器发出错误?

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

我刚刚意识到我在枚举中的“必须处理”值列表中添加了一个值,但直到运行时我才发现它。 我知道 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());
}

这正是为什么我们需要在所有解决方案中进行测试的最佳示例。 您必须编写一个可以枚举您的 Enum 并调用包含 switch case 的方法的测试。 使用此测试,每次编辑 Enum 但忘记更新 switch case 时,您都会得到一个失败的测试。

    [Test]
    public void CheckThatAllEnumCoveredInSwitchCase()
    {

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

    }

如果枚举大小,您可以尽早抛出异常,希望在对枚举进行更改时尽早提醒您:

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# 编译器没有内置此检查,但有一个适用于 .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.HiE.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

对于 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");
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM