C# 当一个未知值被传递到switch语句中时,我应该引发什么类型的异常 编辑1

C# 当一个未知值被传递到switch语句中时,我应该引发什么类型的异常 编辑1,c#,exception-handling,C#,Exception Handling,已更新以使枚举不是方法的参数 问题: switch语句中的枚举经常出现这种问题。在示例代码中,开发人员说明了程序当前使用的所有国家,但如果将另一个国家添加到国家枚举,则应引发异常。我的问题是,应该抛出什么类型的异常 示例代码: 我看过 ,当请求的方法或操作未实现时引发的异常 有些方法在基类中不受支持,期望这些方法将在派生类中实现。派生类可能只实现基类中方法的一个子集,并对不受支持的方法抛出NotSupportedException。 对于对象有时可能执行请求的操作,并且对象状态决定是否可以执行

已更新以使枚举不是方法的参数

问题: switch语句中的枚举经常出现这种问题。在示例代码中,开发人员说明了程序当前使用的所有国家,但如果将另一个国家添加到国家枚举,则应引发异常。我的问题是,应该抛出什么类型的异常

示例代码: 我看过

  • ,当请求的方法或操作未实现时引发的异常
  • 有些方法在基类中不受支持,期望这些方法将在派生类中实现。派生类可能只实现基类中方法的一个子集,并对不受支持的方法抛出NotSupportedException。
    对于对象有时可能执行请求的操作,并且对象状态决定是否可以执行该操作的场景,请参阅InvalidOperationException
  • 在调用方法失败是由无效参数以外的原因引起的情况下使用

我猜不是没有实现就是操作无效。我应该用哪一个?有人有更好的选择吗(我知道滚动你自己的总是一个选择)

我会选择
ArgumentException
,因为agrument是无效的

编辑:


还有
InvalidEnumArgumentException
,它可能更准确地描述了这个问题,但是,我以前从未见过有人使用过它。

不可能传递另一个值,因为枚举将可能的值限制为您处理的值。所以您不需要任何异常。

在您列出的异常中,只有
InvalidOperationException
适合您的场景。我会考虑使用这一点或更具体的,因为您的代码>开关>代码>值作为参数提供。

或者,就像你说的,自己滚


编辑:根据您更新的问题,如果您想使用框架异常,我建议您使用
InvalidOperationException
。但是,对于这种更一般的情况,我当然更愿意使用我自己的异常类型-您不能保证调用堆栈中的其他地方不会捕获到
InvalidOperationException
(可能是框架本身!),因此使用您自己的异常类型更为健壮。

就我个人而言,我不认为这是一个适合任何例外的地方。如果添加国家/地区,则应在switch语句中添加案例。代码不应该因为向枚举添加了值而中断

Eric Lippert有一篇关于何时使用异常的文章,它将您正在寻找的异常类型分类为:(请原谅这不是我的措辞)

bonehead异常是您自己的错误,您本可以阻止它们,因此它们是代码中的错误<你不应该抓住他们;这样做会在代码中隐藏一个bug。相反,您应该编写代码,使异常首先不可能发生,因此不需要捕获


一个选项是在调试模式下执行几乎一个方法契约检查。为美观的表单添加扩展方法:

[Conditional("DEBUG")]
public static bool AssertIsValid(this System.Enum value)
{
    if (!System.Enum.IsDefined(value.GetType(), value))
        throw new EnumerationValueNotSupportedException(value.GetType(), value); //custom exception
}
我想它可能只有在调试模式下才能通过开发/测试环境和单元测试,在生产环境中没有开销(尽管这取决于您)

我建议这实际上是您的
GetCountry
方法的责任。它应该识别
countryId
无效,并引发异常

无论如何,这也应该被单元测试捕获,或者以某种方式更好地处理。无论在何处将字符串/int转换为enum,都应该由一个单数方法处理,该方法可以检查/抛出(就像任何
Parse
方法一样),并有一个检查所有有效数字的单元测试

一般来说,我不认为各种
参数异常
(以及类似情况)是一个很好的候选,因为有几个条件(非参数)情况。我认为,如果您将检查代码移动到一个位置,您还可以抛出自己的异常,该异常可以准确地与任何正在侦听的开发人员进行通信

编辑:考虑到讨论,我认为这里有两个特殊的案例

案例1:将基础类型转换为等效枚举 如果您的方法采用某种类型的输入数据(字符串、int、Guid?),则执行到枚举的转换的代码应验证您是否具有可用的实际枚举。这就是我在上面的回答中提到的情况。在这种情况下,可能会引发您自己的异常,或者可能引发
InvalidEnumArgumentException

这应该像对待任何标准输入验证一样对待。在您的系统中的某个地方,您正在提供垃圾,所以请像处理任何其他解析机制一样处理它

var country = GetCountry(countryId);

switch (country)
{
    case Country.UnitedStates:
        return "1";
    case Country.Mexico:
        return "52";
}

private Country GetCountry(Guid countryId)
{
    //get country by ID
    
    if (couldNotFindCountry)
        throw new EnumerationValueNotSupportedException(.... // or InvalidEnumArgumentException

    return parsedCountry;
}
编辑:当然,编译器要求您的方法抛出/返回,所以不太确定您应该在这里做什么。我想这取决于你。如果真的发生了这种情况,那么它可能是一个BoneHeadeException异常(下面的案例2),因为您通过了输入验证,但没有更新开关/案例来处理新值,所以它可能应该
抛出新的BoneHeadeException()

案例2:添加代码中开关/案例块未处理的新枚举值 如果您是代码的所有者,这属于Eric Lippert在@NominSim的回答中描述的“骨头”异常。尽管这实际上根本不会导致异常,同时使程序处于异常/无效状态

最好的是,在任何一个执行开关的情况下(或类似的情况)都是针对枚举运行的,您应该考虑编写一个单元TE。

public string GetCallingCode(Guid countryId)
{
    var country = GetCountry(countryId);
    country.AssertIsValid(); //throws if the country is not defined
    
    switch (country)
    {
        case Country.UnitedStates:
            return "1";
        case Country.Mexico:
            return "52";
    }
}
var country = GetCountry(countryId);

switch (country)
{
    case Country.UnitedStates:
        return "1";
    case Country.Mexico:
        return "52";
}

private Country GetCountry(Guid countryId)
{
    //get country by ID
    
    if (couldNotFindCountry)
        throw new EnumerationValueNotSupportedException(.... // or InvalidEnumArgumentException

    return parsedCountry;
}