将未知值传递给switch语句时,我应该抛出什么类型的Exception

时间:2012-10-16 14:04:19

标签: c# exception-handling

编辑1

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

问题

这种类型的问题在switch语句中的枚举中出现了很多。在示例代码中,开发人员已考虑该程序当前使用的所有国家/地区,但如果将另一个国家/地区添加到Country枚举,则应抛出异常。我的问题是,应该抛出什么类型的异常?

示例代码:

enum Country
{
    UnitedStates, Mexico,
}

public string GetCallingCode(Guid countryId){
    var country = GetCountry(countryId);
    switch (country)
    {
        case Country.UnitedStates:
            return "1";
            break;
        case Country.Mexico:
            return "52";
            break;
        default:
            // What to throw here
        break;
    }
}

我看了

  • NotImplemented未实现请求的方法或操作时引发的异常。
  • NotSupported 基类中不支持某些方法,期望这些方法将在派生类中实现。派生类可能只实现基类中方法的子集,并为不支持的方法抛出NotSupportedException。
    对于有时可能对象执行请求的操作并且对象状态确定是否可以执行操作的情况,请参阅InvalidOperationException。
  • InvalidOperation 用于无法调用方法的原因是由无效参数以外的原因引起的。

我的猜测是NotImplemented或Invalid Operation。我应该使用哪一个?有人有更好的选择(我知道滚动你自己总是一个选项)

6 个答案:

答案 0 :(得分:10)

我会选择ArgumentException,因为该批评无效。

编辑:http://msdn.microsoft.com/en-us/library/system.argumentexception%28v=vs.71%29.aspx

还有InvalidEnumArgumentException,可能更准确地描述问题,但是,我之前没有看到任何人使用它。

答案 1 :(得分:2)

一种选择是在调试模式下进行几乎一个方法合同检查。抛出一个外观漂亮的扩展方法:

[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
}

我想也许只有它在​​调试模式下,所以它通过你的开发/测试环境和单元测试,在生产中没有开销(虽然这取决于你)

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";
    }
}

我建议尽管这实际上是您GetCountry方法的责任。 应该认识到countryId无效并抛出异常。

无论如何,这应该真的被你的单元测试所捕获,或者以某种方式更好地处理。无论你将字符串/ int转换成你的枚举,都应该用一个单一的方法来处理,而这个方法又可以检查/抛出(就像任何Parse方法一样)并且有一个单元测试来检查所有有效的数字。

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

编辑:考虑到讨论,我认为这里有两个特殊情况。

案例1:将基础类型转换为等效的枚举

如果您的方法采用某种输入数据(string,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;
}

编辑:当然,编译器要求你的方法抛出/返回,所以不太确定你应该在这里做什么。我想这取决于你。如果实际发生这种情况,那么它可能是一个骨头异常(下面的情况2),因为你传递了输入验证没有更新switch / case以处理新值,所以也许它应该{ {1}};

案例2:在代码中添加未由交换机/案例块处理的新枚举值

如果您是代码的所有者,则属于" Boneheaded" Eric Lippert在@NominSim的回答中描述的例外情况。虽然这实际上不会导致在所有处出现异常,同时使程序处于异常/无效状态。

最好的情况是,你执行switch / case(或类似)的任何地方都可以针对枚举运行,你应该考虑编写一个自动运行方法的单元测试所有定义的枚举值。因此,如果您是懒惰或意外错过了一个区块,您的单元测试将警告您没有更新方法来解释枚举列表中的更改。

最后,如果您的枚举来自第三方,而您没有意识到他们更新了这些值,您应该编写一个快速单元测试来验证所有预期值。因此,如果您编写的程序包含throw new BoneheadedException()UnitedStates的检查,那么您的单元测试应该只是这些值的开关/案例块并抛出异常,否则会警告您何时/如果他们最终添加{ {1}}。如果在更新第三方库后该测试失败,您就知道必须在哪些/哪里进行更改才能兼容。

所以在这个"案例2"中,你应该抛出你想要的任何旧例外,因为只要它准确地与通信,它就会由你的单元测试处理。你单位的消费者会测试究竟缺少的东西。


在任何一种情况下,我都不认为交换机/案例代码应该过多地关注无效输入而不是在那里抛出异常。它们应该在设计时抛出(通过单元测试)或在验证/解析输入时抛出(因此抛出适当的"解析/验证"例外)


编辑:我偶然发现post from Eric Lippert讨论了C#编译器如何检测具有返回值的方法是否达到其终点"没有回来。编译器擅长保证有时无法访问端点,但在上述情况下,我们的开发人员知道它无法访问(上述情况除外Mexico发挥作用)。

没有讨论(至少我看到)作为开发人员应该做些什么来解决这些案例。编译器要求您提供返回值或抛出异常,即使您知道它将永远不会到达该代码。在这种情况下,谷歌搜索并没有神奇地浮现一些例外(虽然我无法找出好的搜索条件),而且我宁愿抛出异常并被告知我的假设< / em>它无法到达终点是不正确的,而不是返回某些值,这可能不会告诉我问题或导致不需要的行为。在这种情况下,某些某种Canada可能最有效。当我有时间的时候,我会做更多的挖掘,也许会在programmers上发一个问题来争取一些讨论。

答案 2 :(得分:1)

在您列出的例外情况中,只有InvalidOperationException符合您的情况。我会考虑使用这个,或ArgumentException或更具体的ArgumentOutOfRangeException,因为您的switch值是作为参数提供的。

或者,正如你所说,滚动你自己。

编辑:根据您更新的问题,如果您想使用框架例外,我建议InvalidOperationException。但是,对于这个更通用的情况,我肯定更喜欢自己滚动 - 你不能保证InvalidOperationException不会被调用堆栈中的其他地方捕获(可能是由框架本身!),所以使用你自己的异常类型更加健壮。

答案 3 :(得分:1)

如果您使用的值是纯粹属于对象当前状态的产品,我会使用InvalidOperationException。正如它所说:

  

当方法调用对象的当前状态无效时引发的异常。

即使您更新了问题,由于您无法正确处理的特定值来自传递给它的参数,我仍然会使用ArgumentException - 您可以在错误消息中解释您的信息来自参数的派生与您可以处理的任何内容都不匹配。


对于NotImplementedExceptionNotSupportedException,期望的是,无论来电者做什么,他们都无法纠正这种情况。而ArgumentExceptionInvalidOperationException是线索,如果调用者使用不同的参数,或者将对象转换为另一个状态(分别),则调用可能有效。

答案 4 :(得分:-1)

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

Eric Lippert在使用例外时有article,它将您要查找的例外类型分类为:(原谅不是我的措辞)

  

斩首例外是您自己的错误,您可能已经阻止它们,因此它们是您代码中的错误你不应该抓住它们;这样做会隐藏代码中的错误。相反,您应该编写代码,以便首先不可能发生异常,因此不需要捕获。< / p>

答案 5 :(得分:-2)

传递另一个值是不可能的,因为你的枚举会将可能的值限制为你处理的值。所以你不需要任何例外。