简体   繁体   English

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

[英]What type of Exception should I throw when an unknown value is passed into a switch statement

Edit 1 编辑1

Updated to make the enum not an argument to the method... 更新以使枚举不是方法的参数...

Question

This type of problem comes up a lot with enums in switch statements. 使用switch语句中的枚举会出现这种类型的问题。 In the example code, the developer has accounted for all countries the program is currently using, but if another country is added to the Country enum, an exception should be thrown. 在示例代码中,开发人员已考虑该程序当前使用的所有国家/地区,但如果将另一个国家/地区添加到Country枚举,则应抛出异常。 My question is, what type of exception should be thrown? 我的问题是,应该抛出什么类型的异常?

Example Code: 示例代码:

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

I've looked at 我看了看

  • NotImplemented , The exception that is thrown when a requested method or operation is not implemented. NotImplemented未实现请求的方法或操作时引发的异常。
  • NotSupported There are methods that are not supported in the base class, with the expectation that these methods will be implemented in the derived classes instead. NotSupported 基类中不支持某些方法,期望这些方法将在派生类中实现。 The derived class might implement only a subset of the methods from the base class, and throw NotSupportedException for the unsupported methods. 派生类可能只实现基类中的方法的子集,并为不支持的方法抛出NotSupportedException。
    For scenarios where it is sometimes possible for the object to perform the requested operation, and the object state determines whether the operation can be performed, see InvalidOperationException. 对于有时可能对象执行请求的操作,并且对象状态确定是否可以执行操作的情况,请参阅InvalidOperationException。
  • InvalidOperation is used in cases when the failure to invoke a method is caused by reasons other than invalid arguments. 如果调用方法失败是由无效参数以外的原因引起的,则使用 InvalidOperation

My guess is either NotImplemented or Invalid Operation. 我的猜测是NotImplemented或Invalid Operation。 Which one should I use? 我应该使用哪一个? Does someone have a better option (I know rolling your own is always an option) 有人有更好的选择(我知道滚动你自己总是一个选项)

I would go with ArgumentException , as the agrument is invalid. 我会使用ArgumentException ,因为agrument无效。

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

There is also InvalidEnumArgumentException , which might more accurately describe the problem, however, I have not seen anyone use it before. 还有InvalidEnumArgumentException ,它可能更准确地描述问题,但是,我之前没有看到任何人使用它。

One option is to do almost a method contracts check in Debug mode. 一种选择是在调试模式下进行几乎一个方法合同检查。 Throw in an extension method for nice looking form: 为了漂亮的外观,请使用扩展方法:

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

I figured maybe only have it in debug mode so it passes your development/test environment and unit tests and in production there's no overhead (though that's up to you) 我想也许只有它在​​调试模式下,所以它通过你的开发/测试环境和单元测试,在生产中没有开销(虽然这取决于你)

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

I would suggest though that this is actually the responsibility of your GetCountry method. 我建议尽管这实际上是你的GetCountry方法的责任。 It should recognize that the countryId is not valid and throw an exception. 应该认识到countryId无效并抛出异常。

Regardless, this should really be caught by your unit tests too or somehow better handled. 无论如何,这应该真的被你的单元测试所捕获,或者以某种方式更好地处理。 Wherever you convert a string/int into your enum should be handled by a singular method which in turn can check/throw (just as any Parse method should) and have a unit test that checks all valid numbers. 无论你将字符串/ int转换成你的枚举,都应该用一个奇异的方法来处理,而这个方法又可以检查/抛出(就像任何Parse方法一样)并且有一个单元测试来检查所有有效的数字。

In general, I don't think the various ArgumentExceptions (and the like) are a good candidate because there are several conditions (non-argument) cases. 一般来说,我不认为各种ArgumentExceptions (等)是一个很好的候选者,因为有几个条件(非参数)的情况。 I think if you move the checking code to a single spot, you may as well throw your own exception that accurately communicates to any developer listening. 我认为如果您将检查代码移动到一个位置,您也可以抛出自己的异常,准确地与任何开发人员进行通信。

EDIT: Considering the discussion, I think there are two particular cases here. 编辑:考虑到讨论,我认为这里有两个特殊情况。

Case 1: Converting underlying type to an equivalent enum 情况1:将基础类型转换为等效的枚举

If your methods take some sort of input data (string, int, Guid?), your code performing the conversion into the enum should validate that you have an actual enum that's usable. 如果你的方法采用某种输入数据(string,int,Guid?),你执行转换为枚举的代码应验证你有一个可用的实际枚举。 This is the case I posted above in my answer. 这是我在上面的答案中发布的情况。 In such cases, likely throwing your own exception or possibly InvalidEnumArgumentException . 在这种情况下,可能会抛出您自己的异常或可能是InvalidEnumArgumentException

This should be treated pretty much like any standard input validation. 这应该被视为与任何标准输入验证非常相似。 Somewhere in your system you are providing garbage-in so handle it as you would any other parsing mechanism. 在你的系统中的某个地方你提供垃圾进来,所以像处理任何其他解析机制一样处理它。

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

EDIT: of course, the compiler requires that your method throws/returns, so not so sure what you should do here. 编辑:当然,编译器要求你的方法抛出/返回,所以不太确定你应该在这做什么。 I guess that's up to you. 我想这取决于你。 If that actually happens, it probably is a bone-headed exception (case 2 below) since you passed your input validation yet did not update the switch/case to handle the new value, so maybe it should throw new BoneheadedException() ; 如果实际发生这种情况,它可能是一个骨头异常(下面的情况2),因为你传递了输入验证没有更新switch / case来处理新值,所以它可能会throw new BoneheadedException() ;

Case 2: Adding a new enumeration value which is not handled by your switch/case blocks in your code 情况2:添加一个新的枚举值,该值不会被代码中的switch / case块处理

If you are the owner of your code, this falls under the "Boneheaded" exceptions described by Eric Lippert in @NominSim's answer. 如果您是代码的所有者,则属于Eric Lippert在@ NominSim的回答中描述的“Boneheaded”例外情况。 Though this can actually not result in an exception at all while leaving the program in an exceptional/invalid state. 虽然这可能实际上不会导致异常同时使程序处于异常/无效状态。

The best for this is likely any place where you perform switch/case (or of the like) runs against the enumeration, you should consider writing a unit test that automatically runs the method against all defined values of your enumeration. 最好的情况是,您执行switch / case(或类似)的任何地方都可以针对枚举运行,您应该考虑编写一个单元测试,根据枚举的所有定义值自动运行该方法。 So if you are lazy or accidentally missed a block, your unit tests will warn you that you did not update a method to account for the change in your enumeration listing. 因此,如果您是懒惰或意外错过了一个区块,您的单元测试将警告您没有更新方法来考虑枚举列表中的更改。

Finally, if your enum is coming from a 3rd party which you did not realize they updated the values, you should write a quick unit test that validates all your expected values. 最后,如果您的枚举来自第三方,而您没有意识到他们更新了这些值,您应该编写一个快速单元测试来验证所有预期值。 So if you wrote your program with checks for UnitedStates and Mexico , your unit test should just be a switch/case block for those values and throw an exception otherwise warning you when/if they end up adding Canada . 所以,如果你检查写程序UnitedStatesMexico ,单元测试应该仅仅是为那些值的开关/ case块,并抛出一个异常,否则警告你当/如果他们最终加入Canada When that test fails after updating the 3rd party library, you know what/where you have to make changes to be compatible. 更新第三方库后,如果该测试失败,您就知道必须进行哪些/哪里进行更改才能兼容。

So in this "Case 2", you should throw any old exception you want since it will be handled by your unit tests just so long as it accurately communicates to you or consumers of your unit tests just what exactly is missing. 所以在这个“案例2”中,你应该抛出你想要的任何旧例外,因为只要它准确地与或你单位测试的消费者沟通,究竟缺少什么,它就会被你的单元测试处理。


In either case, I don't think the switch/case code should be caring too much about invalid input and not throwing exceptions there. 在任何一种情况下,我都不认为开关/案例代码应该过多地关注无效输入而不是在那里抛出异常。 They should either be thrown at design time (via unit tests) or thrown when validating/parsing input (and therefore throw appropriate "parsing/validation" exceptions) 它们应该在设计时抛出(通过单元测试)或在验证/解析输入时抛出(因此抛出适当的“解析/验证”异常)


EDIT: I stumbled upon a post from Eric Lippert discussing how the C# compiler detects if a method with a return value ever hits its "end point" without returning. 编辑:我偶然发现了Eric Lippert的一篇文章,讨论了C#编译器如何检测返回值的方法是否会在没有返回的情况下达到其“终点”。 The compiler is good at guaranteeing that the end point is unreachable sometimes, but in cases as yours above, we developers know it's unreachable (except in the circumstances noted above where BoneheadedExceptions come into play). 编译器擅长保证有时无法访问端点,但在上述情况下, 我们的开发人员知道它无法访问(除了上面提到的BoneheadedExceptions发挥作用的情况)。

What wasn't discussed (at least that I saw) what you should do as a developer to resolve these cases. 什么没有讨论(至少我看到)作为开发人员应该做什么来解决这些情况。 The compiler requires that you provide a return value or throw an exception even if you know it will never reach that code. 编译器要求您提供返回值或抛出异常,即使您知道它永远不会到达该代码。 Googling hasn't magically surfaced some exception to leverage in this case (though I couldn't figure out good search terms), and I'd rather throw an exception and be informed that my assumption that it cannot reach the end is incorrect rather than returning some value which may not inform me of the problem or result in unwanted behaviour. 在这种情况下,谷歌搜索没有神奇地浮现出一些例外(虽然我无法找出好的搜索条件),我宁愿抛出异常并被告知我的假设它无法到达终点是不正确的而不是返回一些可能不会告诉我问题或导致不良行为的值。 Maybe some UnexpectedCodePathFailedToReturnValueException of some sort would be most valid in this case. 在这种情况下,某些某种类型的UnexpectedCodePathFailedToReturnValueException可能最有效。 When I have some time, I'll do some more digging and maybe post a question on programmers to garner some discussion. 当我有时间的时候,我会做更多的挖掘,也许会向程序员发一个问题来争取一些讨论。

Of the exceptions you've listed, only InvalidOperationException fits your scenario. 在您列出的例外情况中,只有InvalidOperationException适合您的方案。 I would consider using either this, or either ArgumentException or the more specific ArgumentOutOfRangeException since your switch value is provided as an argument. 我会考虑使用这个,或ArgumentException或更具体的ArgumentOutOfRangeException因为您的switch值是作为参数提供的。

Or, as you say, roll your own. 或者,正如你所说,滚动你自己。

EDIT: Based on your updated question, I would suggest InvalidOperationException if you want to use a framework exception. 编辑:根据您更新的问题,如果您想使用框架异常,我会建议InvalidOperationException However, for this more generic case, I would definitely prefer to roll my own - you can't guarantee that InvalidOperationException won't be caught elsewhere in the callstack (possibly by the framework itself!), so using your own exception type is much more robust. 但是,对于这种更通用的情况,我肯定更喜欢自己滚动 - 你不能保证InvalidOperationException不会被调用堆栈中的其他地方捕获(可能是由框架本身!),所以使用你自己的异常类型很多更强大。

I would use InvalidOperationException if the value you're working with is a product purely of your object's current state. 如果您使用的值是纯粹属于对象当前状态的产品,我会使用InvalidOperationException As it says: 正如它所说:

The exception that is thrown when a method call is invalid for the object's current state. 方法调用对于对象的当前状态无效时引发的异常。

Even with your updated question, since the particular value you cannot deal properly with was derived from an argument passed to it, I would still use an ArgumentException - you can explain in the error message that information you're derived from the argument doesn't match anything you can deal with. 即使你的更新问题,由于你无法正确处理的特定值是从传递给它的参数派生的,我仍然会使用ArgumentException - 你可以在错误消息中解释你从参数派生的信息不是匹配你可以处理的任何事情。


For both NotImplementedException and NotSupportedException the expectation is that, no matter what the caller does, they're not going to be able to remedy the situation. 对于NotImplementedExceptionNotSupportedException ,期望的是,无论调用者做什么,他们都无法纠正这种情况。 Whereas ArgumentException and InvalidOperationException are clues that, if the caller would use a different argument, or transition the object to another state (respectively), the call might work. ArgumentExceptionInvalidOperationException是线索,如果调用者使用不同的参数,或者将对象转换为另一个状态(分别),则调用可能有效。

Personally, I don't think this is the proper place for any Exception at all. 就个人而言,我认为这根本不适合任何例外。 If you add a Country, you should add a case to the switch statement. 如果添加国家/地区,则应在switch语句中添加大小写。 The code shouldn't break because you add a value to an enum. 代码不应该因为您向枚举添加值而中断。

There is an article on when to use exceptions by Eric Lippert, that categorizes the type of exception you are looking for as: (forgive the wording it is not mine) 一篇关于何时使用Eric Lippert的例外的文章 ,它将您正在寻找的例外类型分类为:(原谅不是我的措辞)

Boneheaded exceptions are your own darn fault, you could have prevented them and therefore they are bugs in your code . Boneheaded异常是你自己的错误, 你可以防止它们,因此它们是你的代码中的错误 You should not catch them; 你不应该抓住它们; doing so is hiding a bug in your code. 这样做会隐藏代码中的错误。 Rather, you should write your code so that the exception cannot possibly happen in the first place , and therefore does not need to be caught. 相反,您应该编写代码,以便首先不可能发生异常 ,因此不需要捕获。

it's impossible to pass another value, because your enum limits the possible values to the one you handle. 传递另一个值是不可能的,因为你的枚举将可能的值限制为你处理的值。 so you don't need any exception. 所以你不需要任何例外。

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

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