web-dev-qa-db-fra.com

Quelle est la bonne exception à lancer pour les valeurs enum non gérées?

Ceci est un autre cas de mon autre question sur les cas non traités avec enums qu'il m'a été recommandé de poser en tant que question distincte.

Disons que nous avons SomeEnum et qu'une instruction switch le traite comme ceci:

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

Comme vous le voyez, nous gérons toutes les valeurs enum possibles tout en conservant une exception générée par défaut au cas où un nouveau membre serait ajouté et que nous souhaitons nous assurer que nous sommes conscients du traitement manquant.

Ma question est la suivante: quelle est la bonne exception dans les cas où vous souhaitez notifier que le chemin de code donné n’est pas géré/implémenté ou n’aurait jamais dû être visité? Nous avions l'habitude d'utiliser NotImplementedException mais cela ne semble pas être le bon choix. Notre prochain candidat est InvalidOperationException mais le terme ne sonne pas juste. Quel est le bon et pourquoi?

32
Sedat Kapanoglu

Comme il s’agit d’une opération interne qui échoue (produit quelque chose d’invalide), InvalidOperationException est la voie à suivre.

Les docs disent simplement:

L'exception levée lorsqu'un appel de méthode n'est pas valide pour l'état actuel de l'objet.

ce qui est approximatif, parce que l'état actuel de l'objet conduit à une valeur de retour invalide de someOtherFunc; par conséquent, l'appel de someFunc aurait dû être évité en premier lieu.

22
O. R. Mapper

Personnellement, j'ajoute une exception personnalisée à mon projet:

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

Ensuite, je peux juste au besoin:

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

Ainsi, je peux effectuer une recherche sur "UnexpectedEnumValueException <SomeEnum>" lorsque, par exemple, j'ajoute une nouvelle valeur à SomeEnum et que je souhaite rechercher tous les endroits susceptibles d'être affectés par le changement. Le message d'erreur est beaucoup plus clair qu'une exception générique.

27
Pascal

Essayez d’utiliser InvalidEnumArgumentException Class

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

Je pense que cela dépend de la sémantique représentée par l'énum.

InvalidOperationException est approprié s'il représente un état d'objet.

NotSupportedException est approprié s'il représente une fonctionnalité de l'application qui n'est pas prise en charge.

NotImplementedException est approprié pour une fonctionnalité d'application qui n'est pas implémentée actuellement mais qui pourrait l'être dans une version ultérieure.

...

8
Joe

La proposition Resharper pour un boîtier commutateur:

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

Mais cela ne pourrait pas répondre à vos besoins. Sinon, vous pouvez définir un type d'exception personnalisé concernant ce qui est exécuté dans cette fonction: SomeEnumOutOfRangeException ...

4
AlexH

Si une nouvelle valeur est ajoutée et que vous avez oublié de la gérer quelque part, il s’agit d’une erreur de programmation ou de Boneheaded Exception comme Eric Lippert les appelle. Je crée ma propre classe BoneheadedException que je lance chaque fois que je détecte une erreur de programmation pour laquelle aucun type d'exception FCL n'est plus approprié. 

2
Rich Tebb

Ce que j'essaie de faire dans cette situation est d'utiliser un dictionnaire au lieu d'une instruction switch. (En règle générale, les mappages de ce type sont beaucoup mieux définis par un dictionnaire; je considère presque que les instructions switch sont automatiquement des codes, car il existe toujours ou presque toujours de meilleures méthodes pour organiser un tel mappage.)

Si vous utilisez un dictionnaire, si vous essayez d'indexer votre dictionnaire avec une valeur qui n'a pas été prise en compte, vous obtenez une KeyNotFoundException et il n'y a plus aucune raison de demander "que dois-je faire dans le cas par défaut? ". 

1
Dave Cousineau

Cette situation est vraiment un échec du contrat et n'est pas nécessairement spécifique aux enums. Si vous ne pouvez pas utiliser de contrats de code, vous pouvez créer votre propre type d'exception et le lancer.

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

En ce moment, j'écris deux exceptions personnalisées: UnexpectedValueException et UnexpectedTypeException. Celles-ci me semblent être des exceptions très précieuses, car dès que vous avez identifié l'occurrence d'une valeur ou d'un type inattendu (c'est-à-dire supposé ne pas exister), vous devez envoyer un message aussi significatif que 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);    
   }
}
0
Dave Cousineau