Migliore eccezione per un argomento di tipo generico non valido


106

Attualmente sto scrivendo del codice per UnconstrainedMelody che ha metodi generici a che fare con le enumerazioni.

Ora, ho una classe statica con un sacco di metodi che sono solo pensato per essere utilizzato con le enumerazioni "bandiere". Non posso aggiungerlo come vincolo ... quindi è possibile che vengano chiamati anche con altri tipi di enum. In tal caso, vorrei lanciare un'eccezione, ma non sono sicuro di quale lanciare.

Giusto per renderlo concreto, se ho qualcosa del genere:

// 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
}

Qual è la migliore eccezione da lanciare? ArgumentExceptionsembra logico, ma è un argomento di tipo piuttosto che un normale argomento, che potrebbe facilmente confondere le cose. Devo presentare la mia TypeArgumentExceptionclasse? Utilizzare InvalidOperationException? NotSupportedException? Qualunque altra cosa?

Vorrei piuttosto non creare il mio eccezione per questo meno che non sia chiaramente la cosa giusta da fare.


Mi sono imbattuto in questo oggi scrivendo un metodo generico in cui vengono posti requisiti aggiuntivi sul tipo utilizzato che non può essere descritto con vincoli. Sono rimasto sorpreso di non trovare già un tipo di eccezione nel BCL. Ma questo esatto dilemma è stato quello che ho affrontato anche pochi giorni fa nello stesso progetto per un generico che funzionerà solo con un attributo Flags. Spaventoso!
Andras Zoltan

Risposte:


46

NotSupportedException sembra che si adatti perfettamente, ma la documentazione afferma chiaramente che dovrebbe essere usato per uno scopo diverso. Dalle osservazioni della classe MSDN:

Esistono metodi che non sono supportati nella classe base, con l'aspettativa che questi metodi vengano invece implementati nelle classi derivate. La classe derivata potrebbe implementare solo un sottoinsieme dei metodi dalla classe base e generare NotSupportedException per i metodi non supportati.

Naturalmente, c'è un modo in cui NotSupportedExceptionè ovviamente abbastanza buono, soprattutto dato il suo significato di buon senso. Detto questo, non sono sicuro che sia giusto.

Dato lo scopo di Unconstrained Melody ...

Ci sono varie cose utili che possono essere fatte con metodi / classi generici in cui c'è un vincolo di tipo "T: enum" o "T: delegate" - ma sfortunatamente, quelli sono proibiti in C #.

Questa libreria di utilità aggira i divieti usando ildasm / ilasm ...

... sembra che un nuovo Exceptionpotrebbe essere in ordine nonostante l'elevato onere della prova che dobbiamo giustamente soddisfare prima di creare un costume Exceptions. Qualcosa di simile InvalidTypeParameterExceptionpotrebbe essere utile in tutta la libreria (o forse no - questo è sicuramente un caso limite, giusto?).

I clienti dovranno essere in grado di distinguerlo dalle eccezioni BCL? Quando un cliente potrebbe chiamarlo accidentalmente usando una vaniglia enum? Come rispondereste alle domande poste dalla risposta accettata a Quali fattori dovrebbero essere presi in considerazione quando si scrive una classe di eccezione personalizzata?


In effetti, si è quasi tentati di lanciare un'eccezione solo interna in primo luogo, nello stesso modo in cui fa Code Contracts ... Non credo che nessuno dovrebbe coglierla.
Jon Skeet,

Peccato che non possa semplicemente restituire null!
Jeff Sternal,

25
Vado con TypeArgumentException.
Jon Skeet,

L'aggiunta di eccezioni al Framework può comportare un elevato "onere della prova", ma la definizione di eccezioni personalizzate non dovrebbe. Cose come InvalidOperationExceptionsono icky, perché "Foo chiede alla Collection Bar di aggiungere qualcosa che già esiste, quindi Bar lancia IOE" e "Foo chiede alla collection Bar di aggiungere qualcosa, quindi Bar chiama Boz che lancia IOE anche se Bar non se lo aspetta" lanceranno entrambi lo stesso tipo di eccezione; il codice che si aspetta di catturare il primo non si aspetta il secondo. Detto questo ...
supercat

... Penso che l'argomento a favore di un'eccezione Framework qui sia più convincente rispetto a un'eccezione personalizzata. La natura generale di NSE è che quando un riferimento a un oggetto come tipo generale, e alcuni ma non tutti i tipi specifici di oggetto a cui i punti di riferimento supporteranno un'abilità, tentando di usare l'abilità su un tipo specifico che non supporto dovrebbe lanciare NSE. Considero Foo<T>a un "tipo generale" e Foo<Bar>un "tipo specifico" in quel contesto, anche se non esiste una relazione di "eredità" tra di loro.
supercat

24

Eviterei NotSupportedException. Questa eccezione viene utilizzata nel framework in cui un metodo non è implementato ed è presente una proprietà che indica che questo tipo di operazione non è supportato. Non sta qui

Penso che InvalidOperationException sia l'eccezione più appropriata che potresti lanciare qui.


Grazie per l'avviso su NSE. Gradirei anche il contributo dei tuoi colleghi, btw ...
Jon Skeet

Il punto è che la funzionalità di cui Jon ha bisogno non ha nulla di simile nel BCL. Il compilatore dovrebbe prenderlo. Se rimuovi il requisito "proprietà" da NotSupportedException, le cose che hai menzionato (come la raccolta ReadOnly) sono la cosa più vicina al problema di Jon.
Mehrdad Afshari,

Un punto - ho un metodo IsFlags (che deve essere un metodo per essere generico), che è sorta di indicare che questo tipo di operazione non è supportata ... quindi in questo senso NSE sarebbe opportuno. cioè il chiamante può controllare per primo.
Jon Skeet,

@ Jon: Penso che anche se non hai una tale proprietà, ma tutti i membri del tuo tipo si affidano intrinsecamente al fatto che Tè enumdecorato con Flags, sarebbe valido lanciare NSE.
Mehrdad Afshari,

1
@ Jon: StupidClrExceptionfa un nome divertente;)
Mehrdad Afshari,

13

La programmazione generica non dovrebbe generare in fase di esecuzione per parametri di tipo non valido. Non dovrebbe essere compilato, dovresti avere un'applicazione in fase di compilazione. Non so cosa IsFlag<T>()contenga, ma forse puoi trasformarlo in un'applicazione in fase di compilazione, come provare a creare un tipo che è possibile creare solo con i "flag". Forse una traitsclasse può aiutare.

Aggiornare

Se devi lanciare, voterei per InvalidOperationException. Il ragionamento è che i tipi generici hanno parametri e gli errori relativi ai parametri (metodo) sono incentrati sulla gerarchia ArgumentException. Tuttavia, la raccomandazione su ArgumentException lo afferma

se l'errore non coinvolge gli argomenti stessi, è necessario utilizzare InvalidOperationException.

C'è almeno un atto di fede in questo, che le raccomandazioni sui parametri del metodo devono essere applicate anche a parametri generici , ma non c'è niente di meglio nella gerarchia SystemException imho.


1
No, non c'è modo che questo possa essere vincolato in fase di compilazione. IsFlag<T>determina se l'enum è stato [FlagsAttribute]applicato ad esso e CLR non ha vincoli basati sugli attributi. Sarebbe in un mondo perfetto - o ci sarebbe un altro modo per vincolarlo - ma in questo caso semplicemente non funziona :(
Jon Skeet,

(+1 per il principio generale però - mi piacerebbe poterlo vincolare.)
Jon Skeet,

9

Vorrei usare NotSupportedException poiché è quello che stai dicendo. Enumerazioni diverse da quelle specifiche non sono supportate . Ciò sarebbe ovviamente affermato più chiaramente nel messaggio di eccezione.


2
NotSupportedException viene utilizzato per uno scopo molto diverso nella BCL. Non sta qui. blogs.msdn.com/jaredpar/archive/2008/12/12/…
JaredPar,

8

Andrei con NotSupportedException. Anche se ArgumentExceptionsembra a posto, è davvero previsto quando un argomento passato a un metodo è inaccettabile. Un argomento di tipo è una caratteristica che definisce il metodo effettivo che si desidera chiamare, non un vero "argomento". InvalidOperationExceptiondovrebbe essere lanciato quando l'operazione che stai eseguendo può essere valida in alcuni casi ma per la situazione particolare, è inaccettabile.

NotSupportedExceptionviene generato quando un'operazione è intrinsecamente non supportata. Ad esempio, quando si implementa un'interfaccia in cui un particolare membro non ha senso per una classe. Questa sembra una situazione simile.


Mmm. Ancora non del tutto mi sembra giusto, ma penso che sarà la cosa più vicina ad esso.
Jon Skeet,

Jon: non sembra giusto perché ci aspettiamo naturalmente che venga rilevato dal compilatore.
Mehrdad Afshari,

Sì. Questo è uno strano tipo di vincolo che mi piacerebbe applicare ma non posso :)
Jon Skeet,

6

Apparentemente, Microsoft usa ArgumentExceptionper quello, come dimostrato nell'esempio di Expression.Lambda <> , Enum.TryParse <> o Marshal.GetDelegateForFunctionPointer <> nella sezione Eccezioni. Non sono riuscito a trovare alcun esempio che indichi diversamente (nonostante la ricerca di una fonte di riferimento locale per TDelegatee TEnum).

Quindi, penso che sia lecito ritenere che almeno nel codice Microsoft sia una pratica comune da utilizzare ArgumentExceptionper argomenti di tipo generico non validi oltre a quelli variabili di base. Dato che la descrizione dell'eccezione nei documenti non discrimina tra quelle, non è nemmeno troppo eccessiva.

Si spera che decida una volta per tutte la questione.


Un solo esempio nel framework non è sufficiente per me, no, dato il numero di posti in cui penso che MS abbia fatto una scelta sbagliata in altri casi :) Non deriverei TypeArgumentExceptionda ArgumentException, semplicemente perché un argomento di tipo non è un normale discussione.
Jon Skeet

1
Questo è certamente più convincente in termini di "è ciò che la SM fa in modo coerente". Non lo rende più convincente in termini di corrispondenza della documentazione ... e so che ci sono molte persone nel team di C # che si preoccupano profondamente della differenza tra argomenti regolari e argomenti di tipo :) Ma grazie per gli esempi - sono molto utili.
Jon Skeet

@ Jon Skeet: ha apportato una modifica; ora include 3 esempi da diverse librerie MS, tutte con ArgumentException documentata come quella lanciata; quindi, se è una scelta sbagliata, almeno è una scelta sbagliata coerente. ;) Immagino che Microsoft presuma che gli argomenti regolari e gli argomenti di tipo siano entrambi argomenti; e personalmente, penso che tale ipotesi sia abbastanza ragionevole. ^^ '
Alice

Ah, non importa, sembra che tu l'abbia già notato. Sono contento di aver potuto aiutare. ^^
Alice

Penso che dovremo accettare di non essere d'accordo sul fatto che sia ragionevole trattarli allo stesso modo. Certamente non sono la stessa cosa quando si tratta di riflessione, regole linguistiche, ecc ... sono gestite in modo molto diverso.
Jon Skeet


2

Lanciare un'eccezione su misura dovrebbe sempre essere fatto in ogni caso in cui sia discutibile. Un'eccezione personalizzata funzionerà sempre, indipendentemente dalle esigenze degli utenti API. Lo sviluppatore potrebbe rilevare entrambi i tipi di eccezione se non gli interessa, ma se lo sviluppatore necessita di una gestione speciale sarà SOL.


Anche lo sviluppatore dovrebbe documentare tutte le eccezioni lanciate nei commenti XML.
Eric Schneider,

1

Che ne dici di ereditare da NotSupportedException. Anche se sono d'accordo con @Mehrdad sul fatto che abbia più senso, ho sentito il tuo punto di vista sul fatto che non sembra adattarsi perfettamente. Quindi eredita da NotSupportedException e in questo modo le persone che codificano in base alla tua API possono comunque rilevare un'eccezione NotSupportedException.


1

Sono sempre diffidente nel scrivere eccezioni personalizzate, semplicemente per il fatto che non sono sempre documentate chiaramente e creano confusione se non vengono nominate correttamente.

In questo caso, avrei lanciato un'eccezione ArgumentException per il fallimento del controllo dei flag. Dipende tutto dalle preferenze. Alcuni standard di codifica che ho visto arrivano al punto di definire quali tipi di eccezioni dovrebbero essere lanciati in scenari come questo.

Se l'utente stava tentando di passare qualcosa che non era un'enumerazione, avrei lanciato un'eccezione InvalidOperationException.

Modificare:

Gli altri sollevano un punto interessante che questo non è supportato. La mia unica preoccupazione con una NotSupportedException è che generalmente quelle sono le eccezioni che vengono lanciate quando la "materia oscura" è stata introdotta nel sistema, o per dirla in un altro modo, "Questo metodo deve entrare nel sistema su questa interfaccia, ma abbiamo vinto non attivarlo fino alla versione 2.4 "

Ho anche visto NotSupportedExceptions lanciare un'eccezione di licenza "stai eseguendo la versione gratuita di questo software, questa funzione non è supportata".

Modifica 2:

Un altro possibile:

System.ComponentModel.InvalidEnumArgumentException  

Eccezione generata quando si utilizzano argomenti non validi che sono enumeratori.


Lo costringerò a essere un enum (dopo un po 'di jiggery pokery) - sono solo le bandiere che mi preoccupano.
Jon Skeet,

Penso che quei tizi di licenza dovrebbero lanciare un'istanza di una LicensingExceptionclasse che eredita da InvalidOperationException.
Mehrdad Afshari,

Sono d'accordo Mehrdad, le eccezioni sono purtroppo una di quelle aree in cui c'è molto grigio nel quadro. Ma sono sicuro che questo sia lo stesso per molte lingue. (non sto dicendo che tornerei all'errore di runtime 13 di vb6 ehehe)
Peter,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.