抛出未处理的枚举值的正确异常是什么?

时间:2012-11-30 12:21:10

标签: c# .net exception

这是我的other question about unhandled cases with enums的另一个案例,我建议将其作为一个单独的问题提出。

假设我们有SomeEnum并且有一个switch语句处理它,如:

enum SomeEnum
{
  One,
  Two
}

void someFunc()
{
  SomeEnum value = someOtherFunc();
  switch(value)
  {
     case One:
       ... break;
     case Two:
       ... break;
     default:
         throw new ??????Exception("Unhandled value: " + value.ToString());    
  }
}

如您所见,我们处理所有可能的枚举值但仍保留默认值,以防添加新成员,并且我们希望确保我们知道丢失的处理。

我的问题是:在您想要通知未处理/实施给定代码路径或从未访问过的情况下,什么是正确的异常?我们曾经使用NotImplementedException,但它似乎不合适。我们的下一位候选人是InvalidOperationException,但这个词听起来不对。什么是正确的,为什么?

11 个答案:

答案 0 :(得分:27)

我个人在项目中添加了一个自定义异常:

public class UnexpectedEnumValueException<T> : Exception
{
    public UnexpectedEnumValueException( T value )
        : base( "Value " + value + " of enum " + typeof( T ).Name + " is not supported" )
    {
    }
}

然后我可以根据需要选择它:

enum SomeEnum
{
  One,
  Two
}

void someFunc()
{
  SomeEnum value = someOtherFunc();
  switch(value)
  {
   case SomeEnum.One:
    ... break;
   case SomeEnum.Two:
    ... break;
   default:
      throw new UnexpectedEnumValueException<SomeEnum>(value);    
  }
}

这样我就可以搜索&#34; UnexpectedEnumValueException&lt; SomeEnum&gt;&#34;例如,当我向SomeEnum添加新值时,我想找到可能受更改影响的所有位置。错误消息比通用异常更清晰。

答案 1 :(得分:23)

由于内部操作失败(产生无效的内容),InvalidOperationException是可行的方法。

文档只是说:

  

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

大致是合适的,因为对象的当前状态导致someOtherFunc的返回值无效,因此首先应该避免调用someFunc

答案 2 :(得分:20)

尝试使用InvalidEnumArgumentException Class

void someFunc()
{
  SomeEnum value = someOtherFunc();
  switch(value)
  {
     case One:
       ... break;
     case Two:
       ... break;
     default:
          throw new InvalidEnumArgumentException(); 
  }
}

答案 3 :(得分:8)

我认为这取决于枚举所代表的语义。

如果

InvalidOperationException表示对象状态,则它是合适的。

如果

NotSupportedException表示不受支持的应用程序功能,则

NotImplementedException是合适的。

{{3}}适用于当前未实现但可能在将来版本中的应用程序功能。

...

答案 4 :(得分:4)

开关案例的Resharper主张:

switch(parameter)
{
   default:
      throw new ArgumentOutOfRangeException("parameter");
}

但它无法满足您的需求。如果没有,您可以定义关于此函数中执行的内容的自定义异常类型:SomeEnumOutOfRangeException ...

答案 5 :(得分:2)

如果添加了新值并且您忘记在某处处理它,则会出现编程错误,或者Eric Lippert称之为Boneheaded Exception。我创建了自己的BoneheadedException类,每当我检测到编程错误时,我就抛出这个类,而没有更合适的FCL异常类型。

答案 6 :(得分:1)

在这种情况下我尝试做的是使用字典而不是switch语句。 (一般来说,这样的映射可以通过字典更好地定义;我几乎认为switch语句会自动编写代码,因为总是或几乎总是有更好的方法来组织这样的映射。)

如果您使用字典,那么如果您尝试使用尚未计算的值对字典编制索引,您将获得KeyNotFoundException,并且不再有理由问“做什么”我在默认情况下做什么?“

答案 7 :(得分:0)

这种情况实际上是合同失败,并不一定特定于枚举。如果你不能使用代码契约,你可以创建自己的异常类型并抛出它。

case One:
   ... break;
 case Two:
   ... break;
 default:
    throw new ContractViolationException("Invalid enum");

答案 8 :(得分:0)

这几天,我编写了两个自定义例外:UnexpectedValueExceptionUnexpectedTypeException。在我看来,这些似乎是非常有价值的异常,因为一旦您确定出现了意外的(假定不存在)值或类型,就应该尽可能多地传达有意义的消息。

class UnexpectedValueException : Exception {
   public UnexpectedValueException(object pValue)
   : base($"The value '{pValue}' of type '{pValue?.GetType().ToString() ?? "<null>"}' was not expected to exist.") {
   }
}

enum SomeEnum {
   One,
   Two,
   Three
}

void someFunc() {
   SomeEnum value = someOtherFunc();

   switch (value) {
      case One: ... break;
      case Two: ... break;
      default: throw new UnexpectedValueException(value);    
   }
}

答案 9 :(得分:0)

我确定要使用的是ArgumentOutOfRangeException,它带有一个自定义错误消息,该错误消息的灵感来自默认消息InvalidEnumArgumentException(由于它位于System.ComponentModel内,所以我不愿使用它):

参数'{nameof(theArgument)}'({theArgument})的值对于枚举类型'{nameof(TheEnumType)}'无效。

这是它的样子:

return level switch
{
    LogEventLevel.Verbose => "VERBOSE",
    LogEventLevel.Debug => "DEBUG",
    LogEventLevel.Information => "INFO",
    LogEventLevel.Warning => "WARN",
    LogEventLevel.Error => "ERROR",
    LogEventLevel.Fatal => "FATAL",
    _ => throw new ArgumentOutOfRangeException(nameof(level), level,
        $"The value of argument '{nameof(level)}' ({level}) is invalid for enum type '{nameof(LogEventLevel)}'.")
};

答案 10 :(得分:-2)

我会说NotImplementedException,因为你为未实现的枚举值抛出异常。