Meilleure exception pour un argument de type générique non valide

106

J'écris actuellement du code pour UnconstrainedMelody qui a des méthodes génériques à voir avec les enums.

Maintenant, j'ai une classe statique avec un tas de méthodes qui sont ne destinées qu'à être utilisées avec des énumérations "flags". Je ne peux pas ajouter cela comme contrainte ... il est donc possible qu'ils soient également appelés avec d'autres types d'énumérations. Dans ce cas, j'aimerais lancer une exception, mais je ne sais pas laquelle lancer.

Juste pour rendre cela concret, si j'ai quelque chose comme ça:

// Returns a value with all bits set by any values
public static T GetBitMask<T>() where T : struct, IEnumConstraint
{
    if (!IsFlags<T>()) // This method doesn't throw
    {
        throw new ???
    }
    // Normal work here
}

Quelle est la meilleure exception à lancer? ArgumentExceptionCela semble logique, mais c'est un argument de type plutôt qu'un argument normal, ce qui pourrait facilement semer la confusion. Dois-je présenter ma propre TypeArgumentExceptionclasse? Utiliser InvalidOperationException? NotSupportedException? Rien d'autre?

Je plutôt pas créer ma propre exception pour ce à moins qu'il soit clairement la bonne chose à faire.

Jon Skeet
la source
Je suis tombé sur cela aujourd'hui en écrivant une méthode générique où des exigences supplémentaires sont placées sur le type utilisé qui ne peut pas être décrit avec des contraintes. J'ai été surpris de ne pas trouver de type d'exception déjà dans la BCL. Mais ce dilemme exact était celui auquel j'ai également été confronté il y a quelques jours dans le même projet pour un générique qui ne fonctionnera qu'avec un attribut Flags. Effrayant!
Andras Zoltan

Réponses:

46

NotSupportedException semble tout à fait convenable, mais la documentation indique clairement qu'il devrait être utilisé à des fins différentes. D'après les remarques de la classe MSDN:

Il existe des méthodes qui ne sont pas prises en charge dans la classe de base, avec l'espoir que ces méthodes seront implémentées dans les classes dérivées à la place. La classe dérivée peut implémenter uniquement un sous-ensemble des méthodes de la classe de base et lancer NotSupportedException pour les méthodes non prises en charge.

Bien sûr, il y a un moyen par lequel NotSupportedException est évidemment assez bon, surtout compte tenu de son sens commun. Cela dit, je ne sais pas si c'est juste.

Étant donné le but de la mélodie sans contrainte ...

Il y a diverses choses utiles qui peuvent être faites avec des méthodes / classes génériques où il y a une contrainte de type «T: enum» ou «T: delegate» - mais malheureusement, celles-ci sont interdites en C #.

Cette bibliothèque utilitaire contourne les interdictions en utilisant ildasm / ilasm ...

... il semble qu'un nouveau Exceptionsoit en ordre malgré le lourd fardeau de la preuve que nous devons à juste titre satisfaire avant de créer une personnalisation Exceptions. Quelque chose comme ça InvalidTypeParameterExceptionpourrait être utile dans toute la bibliothèque (ou peut-être pas - c'est sûrement un cas de pointe, non?).

Les clients devront-ils être en mesure de distinguer cela des exceptions BCL? Quand un client pourrait-il accidentellement appeler cela en utilisant une vanille enum? Comment répondriez-vous aux questions posées par la réponse acceptée à Quels facteurs doivent être pris en compte lors de l'écriture d'une classe d'exceptions personnalisée?

Jeff Sternal
la source
En fait, il est presque tentant de lancer une exception interne uniquement, de la même manière que les contrats de code ... Je ne crois pas que quiconque devrait l'attraper.
Jon Skeet le
Dommage qu'il ne puisse pas simplement retourner null!
Jeff Sternal
25
Je vais avec TypeArgumentException.
Jon Skeet
L'ajout d'exceptions au Framework peut avoir un «fardeau de preuve» élevé, mais la définition d'exceptions personnalisées ne devrait pas. Des choses comme InvalidOperationExceptionsont dégoûtantes, parce que "Foo demande à la collection Bar d'ajouter quelque chose qui existe déjà, donc Bar lance IOE" et "Foo demande à la collection Bar d'ajouter quelque chose, alors Bar appelle Boz qui lance IOE même si Bar ne s'y attend pas" lancera tous les deux le même type d'exception; le code qui s'attend à attraper le premier ne s'attendra pas à ce dernier. Cela étant dit ...
supercat
... Je pense que l'argument en faveur d'une exception Framework est ici plus convaincant que pour une exception personnalisée. La nature générale de NSE est que lorsqu'une référence à un objet en tant que type général, et à certains mais pas à tous les types spécifiques d'objet pour lesquels les points de référence prendront en charge une capacité, essayant d'utiliser la capacité sur un type spécifique qui ne ne supporte pas, il devrait lancer NSE. Je considérerais a Foo<T>comme un "type général" et Foo<Bar>comme un "type spécifique" dans ce contexte, même s'il n'y a pas de relation "d'héritage" entre eux.
supercat
24

J'éviterais NotSupportedException. Cette exception est utilisée dans le cadre où une méthode n'est pas implémentée et il existe une propriété indiquant que ce type d'opération n'est pas pris en charge. Ça ne rentre pas ici

Je pense qu'InvalidOperationException est l'exception la plus appropriée que vous puissiez lancer ici.

JaredPar
la source
Merci pour les informations sur NSE. Je serais heureux de recevoir les commentaires de vos collègues aussi, au fait ...
Jon Skeet
Le fait est que la fonctionnalité dont Jon a besoin n'a rien de similaire dans la BCL. Le compilateur est censé l'attraper. Si vous supprimez l'exigence de «propriété» de NotSupportedException, les éléments que vous avez mentionnés (comme la collection ReadOnly) sont ce qui se rapproche le plus du problème de Jon.
Mehrdad Afshari
Un point - j'ai une méthode IsFlags (cela doit être une méthode pour être générique) qui indique en quelque sorte que ce type d'opération n'est pas pris en charge ... donc dans ce sens, NSE serait approprié. c'est-à-dire que l'appelant peut vérifier en premier.
Jon Skeet
@Jon: Je pense que même si vous n'avez pas une telle propriété mais que tous les membres de votre type reposent intrinsèquement sur le fait qu'il Test enumdécoré avec Flags, il serait valide de lancer NSE.
Mehrdad Afshari
1
@Jon: StupidClrExceptionfait un nom amusant;)
Mehrdad Afshari
13

La programmation générique ne doit pas lancer lors de l'exécution pour les paramètres de type non valides. Il ne devrait pas compiler, vous devriez avoir une application de temps de compilation. Je ne sais pas quoiIsFlag<T>() contient, mais vous pouvez peut-être transformer cela en une application au moment de la compilation, comme essayer de créer un type qu'il n'est possible de créer qu'avec des «indicateurs». Peut-être qu'une traitsclasse peut vous aider.

Mettre à jour

Si vous devez lancer, je voterais pour InvalidOperationException. Le raisonnement est que les types génériques ont des paramètres et que les erreurs liées aux paramètres (méthode) sont centrées autour de la hiérarchie ArgumentException. Cependant, la recommandation sur ArgumentException indique que

si l'échec n'implique pas les arguments eux-mêmes, alors InvalidOperationException doit être utilisé.

Il y a au moins un acte de foi là-dedans, que les recommandations de paramètres de méthode doivent également être appliquées aux paramètres génériques , mais il n'y a rien de mieux dans la hiérarchie SystemException à mon humble avis.

Remus Rusanu
la source
1
Non, il n'y a aucun moyen que cela puisse être contraint au moment de la compilation. IsFlag<T>détermine si l'énumération s'y est [FlagsAttribute]appliquée et le CLR n'a pas de contraintes basées sur des attributs. Ce serait dans un monde parfait - ou il y aurait un autre moyen de le contraindre - mais dans ce cas, cela ne fonctionne tout simplement pas :(
Jon Skeet
(+1 pour le principe général cependant - j'aimerais pouvoir le contraindre.)
Jon Skeet
9

J'utiliserais NotSupportedException car c'est ce que vous dites. D'autres énumérations que celles spécifiques ne sont pas prises en charge . Cela serait bien entendu indiqué plus clairement dans le message d'exception.

Robban
la source
2
NotSupportedException est utilisé dans un but très différent dans la BCL. Cela ne rentre pas ici. blogs.msdn.com/jaredpar/archive/2008/12/12/…
JaredPar
8

J'irais avec NotSupportedException. Bien que cela ArgumentExceptionsemble correct, il est vraiment attendu lorsqu'un argument passé à une méthode est inacceptable. Un argument de type est une caractéristique déterminante pour la méthode réelle que vous souhaitez appeler, pas un véritable «argument». InvalidOperationExceptiondoit être lancée lorsque l'opération que vous effectuez peut être valide dans certains cas, mais pour la situation particulière, c'est inacceptable.

NotSupportedExceptionest levée lorsqu'une opération n'est pas prise en charge par nature. Par exemple, lors de l'implémentation d'une interface où un membre particulier n'a pas de sens pour une classe. Cela ressemble à une situation similaire.

Mehrdad Afshari
la source
Mmm. Cela ne semble toujours pas bien , mais je pense que ce sera la chose la plus proche.
Jon Skeet
Jon: cela ne semble pas bien car nous nous attendons naturellement à ce qu'il soit capturé par le compilateur.
Mehrdad Afshari
Ouaip. C'est une sorte de contrainte étrange que j'aimerais appliquer mais que je ne peux pas appliquer :)
Jon Skeet
6

Apparemment, Microsoft utilise ArgumentExceptionpour cela, comme illustré sur l'exemple de Expression.Lambda <> , Enum.TryParse <> ou Marshal.GetDelegateForFunctionPointer <> dans la section Exceptions. Je n'ai pas trouvé d'exemple indiquant le contraire non plus (malgré la recherche d'une source de référence locale pour TDelegateet TEnum).

Donc, je pense qu'il est prudent de supposer qu'au moins dans le code Microsoft, c'est une pratique courante à utiliser ArgumentExceptionpour les arguments de type générique non valides en dehors des variables de base. Étant donné que la description de l'exception dans la documentation ne fait pas de distinction entre celles-ci, ce n'est pas non plus trop extensible.

Espérons qu'il décide une fois pour toutes de la question.

Alice
la source
Un seul exemple dans le cadre ne suffit pas pour moi, pas - étant donné le nombre d'endroits où je pense que MS a fait mauvais choix dans d' autres cas :) Je ne découlerait pas TypeArgumentExceptionde ArgumentException, simplement parce qu'un argument de type est pas régulier argument.
Jon Skeet
1
C'est certainement plus convaincant en termes de "c'est ce que MS fait systématiquement". Cela ne rend pas les choses plus convaincantes en termes de correspondance avec la documentation ... et je sais qu'il y a beaucoup de gens dans l'équipe C # qui se soucient profondément de la différence entre les arguments normaux et les arguments de type :) Mais merci pour les exemples - ils sont très utiles.
Jon Skeet
@Jon Skeet: fait une modification; maintenant, il comprend 3 exemples de différentes bibliothèques MS, tous avec ArgumentException documenté comme celui lancé; donc si c'est un mauvais choix, au moins c'est un mauvais choix constant. ;) Je suppose que Microsoft suppose que les arguments normaux et les arguments de type sont tous deux des arguments; et personnellement, je pense qu'une telle hypothèse est tout à fait raisonnable. ^^ '
Alice
Ah, tant pis, vous semblez déjà l'avoir remarqué. Heureux d'avoir pu aider. ^^
Alice
Je pense que nous devrons être d'accord pour ne pas être d'accord sur la question de savoir s'il est raisonnable de les traiter de la même manière. Ce ne sont certainement pas les mêmes en ce qui concerne la réflexion, les règles linguistiques, etc ... ils sont traités très différemment.
Jon Skeet
3

Id aller avec NotSupportedExpcetion.

Carl Bergquist
la source
2

Lancer une exception personnalisée doit toujours être fait dans tous les cas où cela est discutable. Une exception personnalisée fonctionnera toujours, quels que soient les besoins des utilisateurs de l'API. Le développeur pourrait attraper l'un ou l'autre type d'exception s'il ne s'en soucie pas, mais si le développeur a besoin d'un traitement spécial, il sera SOL.

Eric Schneider
la source
Le développeur doit également documenter toutes les exceptions lancées dans les commentaires XML.
Eric Schneider
1

Que diriez-vous d'hériter de NotSupportedException. Bien que je sois d'accord avec @Mehrdad pour dire que cela a le plus de sens, j'entends votre remarque que cela ne semble pas correspondre parfaitement. Alors héritez de NotSupportedException, et de cette façon, les gens qui codent contre votre API peuvent toujours attraper une NotSupportedException.

BGratuit
la source
1

Je me méfie toujours d'écrire des exceptions personnalisées, uniquement au motif qu'elles ne sont pas toujours clairement documentées et qu'elles causent de la confusion si elles ne sont pas nommées correctement.

Dans ce cas, je lancerais une ArgumentException pour l'échec de la vérification des indicateurs. C'est vraiment une question de préférence. Certaines normes de codage que j'ai vues vont jusqu'à définir les types d'exceptions à lancer dans des scénarios comme celui-ci.

Si l'utilisateur essayait de transmettre quelque chose qui n'était pas une énumération, je lancerais une exception InvalidOperationException.

Éditer:

Les autres soulèvent un point intéressant sur le fait que cela n'est pas pris en charge. Ma seule préoccupation avec une NotSupportedException est que ce sont généralement les exceptions qui sont levées lorsque la "matière noire" a été introduite dans le système, ou pour le dire autrement, "Cette méthode doit aller dans le système sur cette interface, mais nous avons gagné ne l’activez pas avant la version 2.4 "

J'ai également vu NotSupportedExceptions être levé comme une exception de licence "vous exécutez la version gratuite de ce logiciel, cette fonction n'est pas prise en charge".

Modifier 2:

Un autre possible:

System.ComponentModel.InvalidEnumArgumentException  

L'exception levée lors de l'utilisation d'arguments non valides qui sont des énumérateurs.

Peter
la source
Je vais le contraindre à être une énumération (après quelques pokery jiggery) - ce ne sont que les drapeaux qui me préoccupent.
Jon Skeet
Je pense que ces types de licences devraient lancer une instance d'une LicensingExceptionclasse héritant de InvalidOperationException.
Mehrdad Afshari
Je suis d'accord Mehrdad, les exceptions sont malheureusement l'un de ces domaines où il y a beaucoup de gris dans le cadre. Mais je suis sûr que c'est la même chose pour de nombreuses langues. (ne pas dire que je reviendrais à l'erreur d'exécution 13 de vb6)
Peter