On choosing the type of exception for an invalid enumeration value

Let's present the following code:
SomeMethod(xxx someArgument)
{
  ...
  switch(someArgument.SomeEnumProperty)
  {
    case xxx:
      ...
    case yyy:
      ...
    default:
      throw new ???????????????? // what type?
  }
  ...
}

That is, a certain object is passed to the method, and its property (of Enum type) has an invalid value for this method. Such a situation can occur when the method intentionally allows only certain enumeration values (and cannot/should not be invoked with other values); or when there is "future-proofing," meaning if a new enumeration element is added in the future that is not supported by this method, the method will immediately notify about it with an exception. The developer can then either fix the calling code or modify the method to support the new enumeration value.

The situation should be clear, so let's return to the main question: what type of exception should be chosen in this or a similar situation?

Immediately, the following options come to mind:

  • ArgumentException
  • ArgumentOutOfRangeException
  • InvalidEnumArgumentException
  • NotSupportedException
  • InvalidOperationException

And beyond that, each to their own. I've even encountered types such as ApplicationException, etc. in respectable open source projects and so on.

Let's consider each of these exception types and decide which of them are suitable (more or less) or not suitable for the described situation.

Let's start with ArgumentException.

Here's what MSDN says about it:

The exception that is thrown when one of the arguments provided to a method is not valid.

That is, this exception is thrown if the argument passed to the method is invalid. Since an object is a collection of its members, in this context, invalidity can also mean a situation where one of the object's properties takes an invalid value for our method. Therefore, overall this type of exception is suitable for our situation.

Let's consider ArgumentOutOfRangeException:

The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method.

In my opinion, this type of exception implies that the value of the argument itself (not its property) should belong to a certain range, meaning the argument itself should be a number or some enumerable type. In our situation, the argument can be any object, and the problem is not in the argument itself, but in its property, so I believe this type of exception is not very suitable for the considered situation.

InvalidEnumArgumentException:

MSDN erroneously states:

The exception thrown when using invalid arguments that are enumerators.

Of course, it should be not enumerators, but enumerations, but let's forgive them this typo :) The name of the exception initially inspires (at least me), but the problem, as with ArgumentOutOfRangeException, is that this type of exception kind of hints that the argument itself should be an enumeration, not its property. So I think, unfortunately, this type of exception does not suit us either.

NotSupportedException:

The exception that is thrown when an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality.

The phrase "method is not supported" is not our case; after all, our method is supported; the problem is just in the argument's property. But the second part of the description ("does not support the invoked functionality") is more interesting.

Imagine we have a configuration object of type MessageConfiguration describing the settings for sending a message. One of the properties of this object can be some enumeration, for example, a property AttachmentCompression { None, Zip, 7Zip } (for particularly meticulous readers: yes, I understand that such an enumeration might violate the Open/Closed Principle, but it's just an example, and you don't always need to worry too much about OCP). So, something like this:

class MessageConfiguration
{
  ...
  public AttachmentCompression AttachmentCompression { get; set; }
  ...
}

enum AttachmentCompression
{
  None,
  Zip,
  7Zip
}

Imagine that a message from our program can be sent in different ways (as an email via SMTP, through a web service, etc.), and in one of the options, attachment compression is not supported. In my opinion, this fits the words "does not support the invoked functionality," and in some situations, NotSupportedException can be thrown for an invalid enumeration value. However, the problem is that in our original example, we want to throw an exception from the default branch, which rather implies that we encountered an unrecognized value.

NotSupportedException is better thrown if the value is recognized (we know about it and allow that it might come our way), but is consciously not supported, for example, in such code:

switch(someArg.SomeEnumProperty)
{
  case A:
   ...
  case B:
   ...
  case C:
  case D:
    // we know about values C & D, but do not support them in this method   
    throw new NotSupportedException(...); 
  default:
    // in case an unrecognized enumeration value is passed
    // or if a value unsupported by the current version of the method is added in the future
    throw new ArgumentException(...);
}

Thus, NotSupportedException is not very suitable for our case (when the exception is thrown from the default branch), although its use is acceptable in other cases.

Let's look at the description of InvalidOperationException from MSDN:

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

That is, this type of exception should be used when a method call is invalid for the current state of the object whose method is being invoked. For example, if we closed a connection, we cannot use the data sending method. Or vice versa, we cannot use data transmission methods if we just created the object but haven't physically opened the connection yet. Therefore, InvalidOperationException is not suitable for our example.

To be fair, it can be noted that InvalidOperationException can be used, for example, in such a situation:

DoOperation(OperationConfiguration oc, ...)
{
   switch(oc.OperationType)
   {
      case OperationType.CreateCommand:
        if(_state == State.Closed)
           throw new InvalidOperationException("Can not create command if connection is not open.");
      ...
   }
}

But such a situation is already different from the initially considered one. That is, InvalidOperationException can be used for an invalid enumeration value for some state of the object, but this post is still about a different situation.

Summary

In summary, based on the above, I believe that the most suitable and universal type of exception for the considered code example (and similar situations) is ArgumentException. The only thing that slightly bothers me is that this type is not very specific (considering the exception hierarchy), which may not be very convenient in situations where the method can throw ArgumentException for some other reasons, and we need to distinguish the problem with an invalid enumeration value from other possible problems also described by the method using ArgumentException.

However, if we slightly modify the initial example so that the method signature was, for example, like this:

SomeMethod(SomeEnumType arg, ...)

then in such a situation, when the argument-enumeration value is invalid, it would be possible to throw both ArgumentOutOfRangeException and InvalidEnumArgumentException, since their descriptions suit such a situation. More specifically, in such a situation, I would choose InvalidEnumArgumentException as the type of exception that most accurately describes the situation.

I hope this post will be useful to someone. And if now at least one less ApplicationException occurs in case of an invalid enumeration value, then I wrote all this not in vain :)

Comments

Popular posts from this blog

How to set up a simple backup on your UGREEN NAS using rsync & cron scheduler

How to connect to your UGREEN NAS via SSH

Fixing low brightness (darkness) in 4K HDR movies