当未知值传递给switch语句时,我应该抛出什么类型的Exception
编辑1
更新以使枚举不是方法的参数…
题
使用switch语句中的枚举会出现这种类型的问题。 在示例代码中,开发人员已经考虑了程序当前正在使用的所有国家/地区,但如果将另一个国家/地区添加到国家/地区枚举中,则应抛出exception。 我的问题是,应该抛出什么类型的exception?
示例代码:
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 , 未实现请求的方法或操作时引发的exception。
- NotSupported 基类中不支持某些方法,期望这些方法将在派生类中实现。 派生类可能只实现基类中方法的子集,并为不支持的方法抛出NotSupportedException。
对于有时可能对象执行请求的操作,并且对象状态确定是否可以执行操作的情况,请参阅InvalidOperationException。 - 如果调用方法失败是由无效参数以外的原因引起的,则使用 InvalidOperation 。
我的猜测是NotImplemented或Invalid Operation。 我应该使用哪一个? 有人有更好的选择(我知道滚动你自己总是一个选项)
我会使用ArgumentException
,因为agrument无效。
编辑: http : //msdn.microsoft.com/en-us/library/system.argumentexception%28v=vs.71%29.aspx
还有InvalidEnumArgumentException
,它可能更准确地描述问题,但是,我之前没有看到任何人使用它。
一种选择是在调试模式下进行几乎一个方法合同检查。 为了漂亮的外观,请使用扩展方法:
[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 }
我想也许只有它在调试模式下,所以它通过你的开发/测试环境和unit testing,在生产中没有开销(虽然这取决于你)
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
无效并抛出exception。
无论如何,这应该真的被你的unit testing所捕获,或者以某种方式更好地处理。 无论你将字符串/ int转换为你的枚举,都应该通过一个奇异的方法来处理,而这个方法又可以检查/抛出(就像任何Parse
方法一样)并且有一个unit testing来检查所有有效的数字。
一般来说,我不认为各种ArgumentExceptions
(等)是一个很好的候选者,因为有几个条件(非参数)的情况。 我认为,如果您将检查代码移动到一个位置,您也可以抛出自己的exception,准确地与任何开发人员进行通信。
编辑:考虑到讨论,我认为这里有两个特殊情况。
案例1:将基础类型转换为等效的枚举
如果你的方法采用某种输入数据(string,int,Guid?),你执行转换为枚举的代码应该validation你有一个可用的实际枚举。 这是我在上面的答案中发布的情况。 在这种情况下,可能会抛出您自己的exception或可能是InvalidEnumArgumentException
。
这应该像任何标准输入validation一样对待。 在你的系统中的某个地方,你提供垃圾进来,所以像处理任何其他解析机制一样处理它。
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; }
编辑:当然,编译器要求你的方法抛出/返回,所以不太确定你应该在这做什么。 我想这取决于你。 如果实际发生这种情况,它可能是一个骨头exception(下面的情况2),因为你传递了输入validation但没有更新switch / case来处理新值,所以也许它应该throw new BoneheadedException()
;
情况2:添加一个新的枚举值,该值不会被代码中的switch / case块处理
如果您是代码的所有者,则属于Eric Lippert在@ NominSim的回答中描述的“Boneheaded”例外情况。 虽然这实际上不会导致exception,同时使程序处于exception/无效状态。
最好的情况是,您执行switch / case(或类似)的任何地方都可以针对枚举运行,您应该考虑编写一个unit testing,根据枚举的所有定义值自动运行该方法。 因此,如果您是懒惰或意外错过了一个区块,您的unit testing将警告您没有更新方法来考虑枚举列表中的更改。
最后,如果您的枚举来自第三方,您没有意识到他们更新了值,您应该编写一个快速unit testing来validation所有预期值。 因此,如果您使用美国和Mexico
支票编写您的程序,您的unit testing应该只是这些值的开关/案例块并抛出exception,否则警告您何时/如果他们最终添加Canada
。 更新第三方库后,如果测试失败,您就知道必须进行哪些/哪里进行更改才能兼容。
所以在这个“案例2”中,你应该抛出你想要的任何旧的例外,因为只要它准确地与你或你的单位测试的消费者沟通,究竟缺少什么,它就会被你的unit testing处理。
在任何一种情况下,我都不认为开关/案例代码应该过多地关注无效输入而不是在那里抛出exception。 它们应该在设计时抛出(通过unit testing)或在validation/解析输入时抛出(因此抛出适当的“解析/validation”exception)
编辑:我偶然发现了Eric Lippert的一篇文章,讨论了C#编译器如何检测返回值的方法是否曾在没有返回的情况下达到其“终点”。 编译器擅长保证有时无法访问端点,但在上述情况下, 我们的开发人员知道它无法访问(除了上面提到的BoneheadedExceptions
发挥作用的情况)。
什么没有讨论(至少我看到)作为开发人员应该做什么来解决这些情况。 编译器要求您提供返回值或抛出exception,即使您知道它永远不会到达该代码。 在这种情况下,谷歌搜索没有神奇地浮现出一些例外(虽然我无法找出好的搜索条件),而且我宁愿抛出exception并被告知我的假设它无法到达终点是不正确的而不是返回一些可能不会告诉我问题或导致不良行为的值。 在这种情况下,某些某种类型的UnexpectedCodePathFailedToReturnValueException
可能最有效。 当我有时间的时候,我会做更多的挖掘,也许会向程序员发一个问题来争取一些讨论。
在您列出的例外情况中,只有InvalidOperationException
适合您的方案。 我会考虑使用这个,或ArgumentException
或更具体的ArgumentOutOfRangeException
因为您的switch
值是作为参数提供的。
或者,正如你所说,滚动你自己。
编辑:根据您更新的问题,如果您想使用框架exception,我会建议InvalidOperationException
。 但是,对于这个更通用的情况,我肯定更喜欢自己滚动 – 你不能保证InvalidOperationException
不会被调用堆栈中的其他地方捕获(可能是由框架本身!),所以使用你自己的exception类型很多更强大。
如果您使用的值是纯粹属于对象当前状态的产品,我会使用InvalidOperationException
。 正如它所说:
方法调用对于对象的当前状态无效时引发的exception。
即使你的更新问题,由于你无法正确处理的特定值是从传递给它的参数派生的,我仍然会使用ArgumentException
– 你可以在错误消息中解释你从参数派生的信息不是匹配你可以处理的任何事情。
对于NotImplementedException
和NotSupportedException
,期望的是,无论调用者做什么,他们都无法纠正这种情况。 而ArgumentException
和InvalidOperationException
是线索,如果调用者使用不同的参数,或者将对象转换为另一个状态(分别),则调用可能有效。
就个人而言,我认为这根本不适合任何例外。 如果添加国家/地区,则应在switch语句中添加大小写。 代码不应该因为您向枚举添加值而中断。
有一篇关于何时使用Eric Lippert的例外的文章 ,它将您正在寻找的例外类型分类为:(原谅不是我的措辞)
Boneheadedexception是你自己的错误, 你可能已经阻止了它们,因此它们是你代码中的错误 。 你不应该抓住它们; 这样做会隐藏代码中的错误。 相反,您应该编写代码,以便首先不可能发生exception ,因此不需要捕获。
传递另一个值是不可能的,因为你的枚举将可能的值限制为你处理的值。 所以你不需要任何例外。