53

This is another case of my other question about unhandled cases with enums which I was recommended to ask as a separate question.

Say we have SomeEnum and have a switch statement handling it like:

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

As you see we handle all possible enum values but still keep a default throwing an exception in case a new member gets added and we want to make sure we are aware of the missing handling.

My question is: what's the right exception in such circumstances where you want to notify that the given code path is not handled/implemented or should have never been visited? We used to use NotImplementedException but it doesn't seem to be the right fit. Our next candidate is InvalidOperationException but the term doesn't sound right. What's the right one and why?

Community
  • 1
  • 1
Sedat Kapanoglu
  • 46,641
  • 25
  • 114
  • 148
  • `GivenCodePathIsNotHandledException` – L.B Nov 30 '12 at 12:24
  • 1
    @AmithGeorge: but it's not an argument? – Sedat Kapanoglu Nov 30 '12 at 12:30
  • It does look like `someOtherFunc` is misbehaving and returning an invalid enum. Shouldn't `someOtherFunc` be the function throwing the exception? It will have a better idea why it generated the invalid value. – Amith George Nov 30 '12 at 12:36
  • @AmithGeorge: The question is specifically about the situation where `someOtherFunc` *doesn't* throw an exception. – O. R. Mapper Nov 30 '12 at 12:36
  • I understand. Which is why I provided a different line of thought in the form of a comment and not a answer. If the OP is not interested in pursuing this line of thought, thats ok :) – Amith George Nov 30 '12 at 12:45
  • 1
    @AmithGeorge: think of them as two separate components developed by different developers. developer B added a new enum return value but developer A isn't aware of it. in this case A's code silently ignores new enum value and potentially creates a hard to detect problem. exception avoids that. – Sedat Kapanoglu Nov 30 '12 at 12:49
  • 1
    True. In light of that, is it not a `NotSupported` situation for `someFunc`? Though I wonder what happens when `someFunc` is meant to only work with those two values. In cases where the addition of a third enum value will not affect the operation of `someFunc`. In those cases it will still throw an Exception. To handle those you will need to add empty case handlers. – Amith George Nov 30 '12 at 12:57

11 Answers11

41

Personally, I add a custom exception to my project:

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

Then I can use it as needed:

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

That way, I can do a search for "UnexpectedEnumValueException<SomeEnum>" when I, for example, a add new value to SomeEnum and I want to find all the places that could be impacted by the change. The error message is much more clear than a generic exception.

Pang
  • 9,564
  • 146
  • 81
  • 122
Pascal
  • 712
  • 6
  • 12
  • This is a popular answer but bear in mind that custom exceptions have gotchas like serialization. I don't recommend using this approach. – Sedat Kapanoglu Nov 11 '20 at 16:08
  • @SedatKapanoglu Is that a non-issue here due to the simplicity of this particular exception? Or is the value variable a problem? – Brian MacKay Dec 09 '21 at 02:15
  • 1
    @BrianMacKay yes, Value is a problem for serialization, but even without custom properties, you need to have the right set of constructors for correct serialization of the type itself. That being said, the importance of serialization might have taken a back seat in .NET core, and simple exception overrides may not be so troublesome anymore. – Sedat Kapanoglu Dec 10 '21 at 03:28
39

As it is an internal operation that fails (produces something invalid), InvalidOperationException is the way to go.

The docs simply say:

The exception that is thrown when a method call is invalid for the object's current state.

which is roughly fitting, because the current state of the object lead to an invalid return value of someOtherFunc, hence the call of someFunc should have been avoided in the first place.

O. R. Mapper
  • 20,083
  • 9
  • 69
  • 114
  • It will depend if the someOtherFunc() gets the enum value from the current object or not, we need a precision – AlexH Nov 30 '12 at 12:55
  • 1
    I find this answer the most practical and most semantically close to all possible cases except argument validation (answered in my other question). Thanks folks. – Sedat Kapanoglu Nov 30 '12 at 13:06
34

Try using InvalidEnumArgumentException Class

void someFunc()
{
  SomeEnum value = someOtherFunc();
  switch(value)
  {
     case One:
       ... break;
     case Two:
       ... break;
     default:
          throw new InvalidEnumArgumentException(); 
  }
}
Furqan Safdar
  • 16,260
  • 13
  • 59
  • 93
14

I think it depends on the semantics represented by the enum.

InvalidOperationException is appropriate if it represents an object state.

NotSupportedException is appropriate if it represents an application feature that isn't supported.

NotImplementedException is appropriate for an application feature that is not currently implemented but might be in a future version.

...

Joe
  • 122,218
  • 32
  • 205
  • 338
  • 4
    +1, While I find your reasoning the most sensible, this level of granularity needs too much thinking ("is this object state or not?", "is this a feature or not?", "could this be implemented in future or not?", and combinations). We'll go with O.R's more practical interpretation of `InvalidOperationException` along with the answer to my other question (`InvalidEnumArgumentException`). Thanks. – Sedat Kapanoglu Nov 30 '12 at 12:59
6

The ReSharper proposition for a switch case:

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

But it might not fit your needs, in which case you can define a custom exception type regarding what is performed in this function: SomeEnumOutOfRangeException...

Pang
  • 9,564
  • 146
  • 81
  • 122
AlexH
  • 2,650
  • 1
  • 27
  • 35
2

If a new value gets added and you've forgotten to handle it somewhere, it's a programming error, or a Boneheaded Exception as Eric Lippert calls them. I create my own BoneheadedException class which I throw whenever I detect a programming error for which no FCL exception type is more appropriate.

Rich Tebb
  • 6,806
  • 2
  • 23
  • 25
  • 3
    :) +1 but in teamwork it's not always about an individual but communication or process. i refuse to label it boneheadedness right away :) – Sedat Kapanoglu Nov 30 '12 at 12:46
1

What I try to do in this situation is use a dictionary instead of a switch statement. (Generally, mappings like this are much better defined by a dictionary; I almost consider switch statements to be automatically code smells, because there are always or almost always better ways of organizing such a mapping.)

If you use a dictionary, then if you try to index your dictionary with a value that hasn't been accounted for, you'll get a KeyNotFoundException, and there's no longer a reason to ask "what do I do in the default case?".

Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
  • 1
    +1, Dictionaries are good but can be inefficient and unnecessarily elaborate for small number of options. Still good to know the right action to take for the switch case. – Sedat Kapanoglu Apr 12 '17 at 21:30
-1

This situation is really a contract failure and isn't necessarily specific to enums. If you can't use code contracts, you can create your own exception type and throw that.

case One:
   ... break;
 case Two:
   ... break;
 default:
    throw new ContractViolationException("Invalid enum");
Lee
  • 142,018
  • 20
  • 234
  • 287
  • I'm not convinced of that interpretation - the code that will receive the exception (caller of `someFunc()`) has not directly or indirectly violated any contract. – O. R. Mapper Nov 30 '12 at 12:40
  • 1
    @O.R.Mapper - Well a contract violation has occured - the caller also hasn't performed an invalid operation or passed an invalid argument. This code path will only be entered in the case of `someOtherFunc` breaking its contract by returning an invalid value for the enum type. – Lee Nov 30 '12 at 12:43
-1

These days I write two custom exceptions: UnexpectedValueException and UnexpectedTypeException. These seem to me like very valuable exceptions to have, since as soon as you've identified an occurrence of an unexpected (meaning assumed not to exist) value or type, you should throw with as meaningful a message as possible.

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);    
   }
}
Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
  • 1
    Writing a custom exception class correctly is a lot of work and requires taking scenarios like serialization into consideration (https://blogs.msdn.microsoft.com/agileer/2013/05/17/the-correct-way-to-code-a-custom-exception-class/). I think `InvalidOperationException` works well for this specific case of enum use. – Sedat Kapanoglu Sep 27 '18 at 22:39
  • @SedatKapanoglu Why does a custom exception have to support serialization? – Dave Cousineau Sep 27 '18 at 23:58
  • @SedatKapanoglu not to mention that `InvalidOperationException` is semantically incorrect. `Invalid Operation` means that it was not ok to call the given method in the object's current state. So you called things in the wrong order, or didn't inject a required dependency yet, or something of that nature. The exception here is that we thought that we had covered all values, yet there is somehow a value outside of the range of values that we assumed existed, which represents that this method is incomplete, or the value that was passed shouldn't normally exist. Hence `UnexpectedValueException`. – Dave Cousineau Sep 28 '18 at 16:52
-1

What I have settled to use is ArgumentOutOfRangeException with a custom error message inspired by the default message of InvalidEnumArgumentException (which I prefer not to use since it's inside System.ComponentModel):

The value of argument '{nameof(theArgument)}' ({theArgument}) is invalid for enum type '{nameof(TheEnumType)}'.

Here is what it looks like:

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)}'.")
};
0xced
  • 25,219
  • 10
  • 103
  • 255
  • But it's not an argument? – Sedat Kapanoglu Nov 11 '20 at 16:04
  • @SedatKapanoglu If you make a method that translates some enum value to another value then it's actually an argument. Otherwise you may just consider the enum value as the argument to the `switch` statement. – 0xced Nov 23 '20 at 18:45
  • "If you make a method that translates some enum value to another value then it's actually an argument", no, not really. "Argument" has a pretty specific definition. https://stackoverflow.com/questions/156767/whats-the-difference-between-an-argument-and-a-parameter – Sedat Kapanoglu Nov 23 '20 at 23:39
-2

I would say NotImplementedException because you are throwing exception for an unimplemented enum value.

J.Wells
  • 1,749
  • 12
  • 13
  • 1
    From [the docs](https://msdn.microsoft.com/en-us/library/system.notimplementedexception(v=vs.110).aspx), it says that *The exception that is thrown when a requested method or operation is not implemented.* This is a stretch, as the enum switch is actually implemented, but it doesn't know how to handle at least one case. If one argues that this is covered by the wording "*or the operation is not implemented*", then so is `InvalidOperationException` without the ambiguity. – sshine Apr 26 '16 at 15:25
  • Ugh... This is voted the least helpful answer here. Given the most popular answer is `InvalidEnumArgumentException`, and that exception seems to perfectly fit the OP's question, why do you feel the need to make these remarks at all? – J.Wells Apr 27 '16 at 12:14
  • 2
    Hi there. I did not intend to be mean, so I'm sorry you took it this way. I didn't find your suggestion bad at all, which is why I felt the need to justify to the potential reader why it wasn't a good choice, rather than have them rely on a negative score alone. (I often find that the less popular answers aren't necessarily bad.) I'll make sure to sound more positive the next time I do something like this. Again, I'm sorry that I offended you. – sshine Apr 27 '16 at 13:02
  • I am not offended. The answer is 4 years old. – J.Wells Apr 27 '16 at 13:26