Beste Ausnahme für ein ungültiges generisches Typargument

106

Ich schreibe gerade Code für UnconstrainedMelody, der generische Methoden für Aufzählungen enthält.

Jetzt habe ich eine statische Klasse mit einer Reihe von Methoden, die nur für "Flags" -Aufzählungen verwendet werden sollen. Ich kann dies nicht als Einschränkung hinzufügen ... daher ist es möglich, dass sie auch mit anderen Aufzählungstypen aufgerufen werden. In diesem Fall würde ich gerne eine Ausnahme auslösen, bin mir aber nicht sicher, welche ich auslösen soll.

Nur um dies konkret zu machen, wenn ich so etwas habe:

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

Was ist die beste Ausnahme zu werfen? ArgumentExceptionklingt logisch, aber es ist eine Art eher Argument als ein normales Argument, das könnte leicht verwirren Dinge. Soll ich meine eigene TypeArgumentExceptionKlasse vorstellen ? Verwenden InvalidOperationException? NotSupportedException? Noch etwas?

Ich würde lieber keine eigene Ausnahme dafür erstellen, es sei denn, es ist eindeutig das Richtige.

Jon Skeet
quelle
Ich bin heute darauf gestoßen, als ich eine generische Methode geschrieben habe, bei der zusätzliche Anforderungen an den verwendeten Typ gestellt werden, die nicht mit Einschränkungen beschrieben werden können. Ich war überrascht, keinen Ausnahmetyp bereits in der BCL zu finden. Aber genau dieses Dilemma hatte ich vor einigen Tagen im selben Projekt für ein Generikum, das nur mit einem Flags-Attribut funktioniert. Gruslig!
Andras Zoltan

Antworten:

46

NotSupportedException klingt so, als würde es eindeutig passen, aber die Dokumentation besagt eindeutig, dass es für einen anderen Zweck verwendet werden sollte. Aus der MSDN-Klasse Anmerkungen:

Es gibt Methoden, die in der Basisklasse nicht unterstützt werden, mit der Erwartung, dass diese Methoden stattdessen in den abgeleiteten Klassen implementiert werden. Die abgeleitete Klasse implementiert möglicherweise nur eine Teilmenge der Methoden aus der Basisklasse und löst NotSupportedException für die nicht unterstützten Methoden aus.

Natürlich gibt es eine Art und Weise, die NotSupportedExceptionoffensichtlich gut genug ist, insbesondere angesichts ihrer Bedeutung für den gesunden Menschenverstand. Trotzdem bin ich mir nicht sicher, ob es genau richtig ist.

Angesichts des Zwecks der unbeschränkten Melodie ...

Es gibt verschiedene nützliche Dinge, die mit generischen Methoden / Klassen ausgeführt werden können, bei denen die Typbeschränkung "T: enum" oder "T: delegate" besteht - diese sind jedoch in C # leider verboten.

Diese Dienstprogrammbibliothek umgeht die Verbote mit ildasm / ilasm ...

... es scheint, als wäre ein neues Exceptionin Ordnung, trotz der hohen Beweislast, die wir zu Recht erfüllen müssen, bevor wir einen Brauch erstellen Exceptions. So etwas InvalidTypeParameterExceptionkönnte in der gesamten Bibliothek nützlich sein (oder auch nicht - dies ist sicherlich ein Randfall, oder?).

Müssen Kunden in der Lage sein, dies von BCL-Ausnahmen zu unterscheiden? Wann könnte ein Kunde dies versehentlich mit einer Vanille aufrufen enum? Wie würden Sie die Fragen beantworten, die sich aus der akzeptierten Antwort auf Welche Faktoren sollten beim Schreiben einer benutzerdefinierten Ausnahmeklasse berücksichtigt werden?

Jeff Sternal
quelle
In der Tat verlockend es ist fast eine Brenn einzige Ausnahme in erster Linie zu werfen, in der gleichen Art und Weise , den Code Verträge nicht ... Ich glaube nicht , dass jemand sollte es werden , zu kontrollieren.
Jon Skeet
Schade, dass es nicht einfach null zurückgeben kann!
Jeff Sternal
25
Ich gehe mit TypeArgumentException.
Jon Skeet
Das Hinzufügen von Ausnahmen zum Framework kann eine hohe "Beweislast" haben, das Definieren von benutzerdefinierten Ausnahmen sollte dies jedoch nicht. Dinge wie InvalidOperationExceptionsind eklig, weil "Foo die Sammlungsleiste auffordert, etwas hinzuzufügen, das bereits vorhanden ist, also wirft Bar IOE" und "Foo die Sammlungsleiste auffordert, etwas hinzuzufügen, also ruft Bar Boz an, der IOE wirft, obwohl Bar dies nicht erwartet". werden beide den gleichen Ausnahmetyp auslösen; Code, der erwartet, den ersten zu fangen, erwartet den letzteren nicht. Das wurde gesagt ...
Superkatze
... Ich würde denken, dass das Argument für eine Framework-Ausnahme hier überzeugender ist als für eine benutzerdefinierte Ausnahme. Die allgemeine Natur von NSE besteht darin, dass bei einem Verweis auf ein Objekt als allgemeinen Typ und einigen, aber nicht allen spezifischen Objekttypen, auf die die Referenzpunkte eine Fähigkeit unterstützen, versucht wird, die Fähigkeit für einen bestimmten Typ zu verwenden, der dies nicht tut Nicht unterstützen sollte es NSE werfen. Ich würde a Foo<T>als "allgemeinen Typ" und Foo<Bar>in diesem Zusammenhang als "spezifischen Typ" betrachten, obwohl zwischen ihnen keine "Vererbungs" -Beziehung besteht.
Supercat
24

Ich würde NotSupportedException vermeiden. Diese Ausnahme wird in dem Framework verwendet, in dem eine Methode nicht implementiert ist und eine Eigenschaft angibt, dass diese Art von Operation nicht unterstützt wird. Es passt hier nicht

Ich denke, InvalidOperationException ist die am besten geeignete Ausnahme, die Sie hier auslösen können.

JaredPar
quelle
Vielen Dank für das Heads-up zu NSE. Würde mich auch über Beiträge von Ihren Kollegen freuen, übrigens ...
Jon Skeet
Der Punkt ist, dass die Funktionalität, die Jon benötigt, in der BCL nichts Ähnliches hat. Der Compiler soll es fangen. Wenn Sie die Anforderung "Eigenschaft" aus NotSupportedException entfernen, sind die von Ihnen erwähnten Dinge (wie die ReadOnly-Auflistung) Jons Problem am nächsten.
Mehrdad Afshari
Ein Punkt - ich habe ein IsFlags Verfahren (es hat eine Methode , um generisch zu sein) , welche Art von darauf hinweist , dass diese Art der Operation wird nicht unterstützt ... In diesem Sinne NSE angemessen wäre. dh der Anrufer kann zuerst prüfen.
Jon Skeet
@ Jon: Ich denke, selbst wenn Sie keine solche Eigenschaft haben, aber alle Mitglieder Ihres Typs von Natur aus auf die Tatsache angewiesen sind, dass Teine mit enumdekoriert ist Flags, wäre es gültig, NSE zu werfen.
Mehrdad Afshari
1
@ Jon: StupidClrExceptionmacht einen lustigen Namen;)
Mehrdad Afshari
13

Generische Programmierung sollte zur Laufzeit keine ungültigen Typparameter auslösen. Es sollte nicht kompiliert werden, Sie sollten eine Durchsetzung der Kompilierungszeit haben. Ich weiß nicht, was es IsFlag<T>()enthält, aber vielleicht können Sie daraus eine Durchsetzung der Kompilierungszeit machen, z. B. den Versuch, einen Typ zu erstellen, der nur mit 'Flags' erstellt werden kann. Vielleicht kann eine traitsKlasse helfen.

Aktualisieren

Wenn Sie werfen müssen , würde ich für InvalidOperationException stimmen. Der Grund dafür ist, dass generische Typen Parameter haben und Fehler in Bezug auf (Methoden-) Parameter um die ArgumentException-Hierarchie zentriert sind. Die Empfehlung zu ArgumentException besagt jedoch, dass

Wenn der Fehler nicht die Argumente selbst betrifft, sollte InvalidOperationException verwendet werden.

Es gibt mindestens einen Vertrauenssprung darin, dass Empfehlungen für Methodenparameter auch auf generische Parameter angewendet werden sollen, aber es gibt nichts Besseres in der SystemException-Hierarchie imho.

Remus Rusanu
quelle
1
Nein, dies kann auf keinen Fall zur Kompilierungszeit eingeschränkt werden. IsFlag<T>Legt fest, ob die Aufzählung darauf [FlagsAttribute]angewendet wurde und die CLR keine auf Attributen basierenden Einschränkungen aufweist. Es würde in einer perfekten Welt - oder es würde einen anderen Weg geben, es einzuschränken - aber in diesem Fall funktioniert es einfach nicht :(
Jon Skeet
(+1 für den allgemeinen Grundsatz , obwohl - ich würde gerne sein kann . Es beschränken)
Jon Skeet
9

Ich würde NotSupportedException verwenden, da dies das ist, was Sie sagen. Andere als die spezifischen Aufzählungen werden nicht unterstützt . Dies würde natürlich in der Ausnahmemeldung deutlicher angegeben.

Robban
quelle
2
NotSupportedException wird in der BCL für einen ganz anderen Zweck verwendet. Es passt hier nicht. blogs.msdn.com/jaredpar/archive/2008/12/12/…
JaredPar
8

Ich würde mitgehen NotSupportedException. Während ArgumentExceptionsieht gut aus , es ist wirklich erwartet , wenn ein Argument an eine Methode übergeben nicht akzeptabel ist. Ein Typargument ist ein definierendes Merkmal für die tatsächliche Methode, die Sie aufrufen möchten, kein echtes "Argument". InvalidOperationExceptionsollte ausgelöst werden, wenn die von Ihnen ausgeführte Operation in einigen Fällen gültig sein kann, aber für die jeweilige Situation nicht akzeptabel ist.

NotSupportedExceptionwird ausgelöst, wenn eine Operation von Natur aus nicht unterstützt wird. Zum Beispiel bei der Implementierung einer Schnittstelle, bei der ein bestimmtes Mitglied für eine Klasse keinen Sinn ergibt. Dies sieht nach einer ähnlichen Situation aus.

Mehrdad Afshari
quelle
Mmm. Es ist noch nicht ganz das Gefühl , richtig, aber ich denke , es ist die nächste Sache, es sein wird.
Jon Skeet
Jon: Es fühlt sich nicht richtig an, weil wir natürlich erwarten, dass es vom Compiler abgefangen wird.
Mehrdad Afshari
Jep. Dies ist eine seltsame Art von Einschränkung, die ich gerne anwenden würde, aber nicht kann :)
Jon Skeet
6

Anscheinend verwendet Microsoft ArgumentExceptiondafür, wie am Beispiel von Expression.Lambda <> , Enum.TryParse <> oder Marshal.GetDelegateForFunctionPointer <> im Abschnitt Ausnahmen gezeigt. Ich konnte auch kein anderes Beispiel finden (obwohl ich in der lokalen Referenzquelle nach TDelegateund gesucht habe TEnum).

Ich denke also, es ist sicher anzunehmen, dass es zumindest in Microsoft-Code eine gängige Praxis ist, ArgumentExceptionungültige generische Typargumente neben einfachen variablen Argumenten zu verwenden. Da die Ausnahmebeschreibung in Dokumenten nicht zwischen diesen unterscheidet, ist sie auch nicht allzu langwierig.

Hoffentlich entscheidet es ein für alle Mal über die Frage.

Alice
quelle
Ein einziges Beispiel im Rahmen ist nicht genug für mich, nein - die Zahl der Plätze gegeben , wo ich denke , MS schlechte Wahl in anderen Fällen gemacht hat :) Ich würde nicht ableiten TypeArgumentExceptionaus ArgumentException, nur weil eine Art Argument ist keine reguläre Streit.
Jon Skeet
1
Das ist sicherlich überzeugender in Bezug auf "es ist das, was MS konsequent tut". Es macht es nicht zwingender, die Dokumentation abzugleichen ... und ich weiß, dass es viele Leute im C # -Team gibt, denen der Unterschied zwischen regulären Argumenten und Typargumenten sehr am Herzen liegt :) Aber danke für die Beispiele - Sie sind sehr hilfreich.
Jon Skeet
@ Jon Skeet: Eine Bearbeitung vorgenommen; Jetzt enthält es 3 Beispiele aus verschiedenen MS-Bibliotheken, von denen alle mit ArgumentException dokumentiert sind. Wenn es also eine schlechte Wahl ist, ist es zumindest eine durchweg schlechte Wahl. ;) Ich denke, Microsoft geht davon aus, dass reguläre Argumente und Typargumente beide Argumente sind. und ich persönlich halte eine solche Annahme für vernünftig. ^^ '
Alice
Ah, egal, anscheinend haben Sie das schon bemerkt. Froh, dass ich helfen konnte. ^^
Alice
Ich denke, wir müssen uns darauf einigen, nicht darüber zu streiten, ob es vernünftig ist, sie gleich zu behandeln. Sie sind sicherlich nicht gleich, wenn es um Reflexion, Sprachregeln usw. geht. Sie werden sehr unterschiedlich behandelt.
Jon Skeet
3

Ich würde mit NotSupportedExpcetion gehen.

Carl Bergquist
quelle
2

Das Auslösen einer benutzerdefinierten Ausnahme sollte immer dann erfolgen, wenn dies fraglich ist. Eine benutzerdefinierte Ausnahme funktioniert immer, unabhängig von den Anforderungen der API-Benutzer. Der Entwickler könnte beide Ausnahmetypen abfangen, wenn es ihn nicht interessiert, aber wenn der Entwickler eine spezielle Behandlung benötigt, ist er SOL.

Eric Schneider
quelle
Außerdem sollte der Entwickler alle Ausnahmen dokumentieren, die in den XML-Kommentaren ausgelöst werden.
Eric Schneider
1

Wie wäre es mit dem Erben von NotSupportedException? Obwohl ich @Mehrdad zustimme, dass es am sinnvollsten ist, höre ich Ihren Standpunkt, dass es nicht perfekt zu passen scheint. Erben Sie also von NotSupportedException, und auf diese Weise können Personen, die gegen Ihre API codieren, weiterhin eine NotSupportedException abfangen.

BKostenlos
quelle
1

Ich bin immer vorsichtig, wenn ich benutzerdefinierte Ausnahmen schreibe, nur weil sie nicht immer klar dokumentiert sind und Verwirrung stiften, wenn sie nicht richtig benannt werden.

In diesem Fall würde ich eine ArgumentException für den Fehler der Flagsprüfung auslösen. Es hängt alles von den Vorlieben ab. Einige Codierungsstandards, die ich gesehen habe, gehen so weit, zu definieren, welche Arten von Ausnahmen in solchen Szenarien ausgelöst werden sollen.

Wenn der Benutzer versuchen würde, etwas zu übergeben, das keine Aufzählung ist, würde ich eine InvalidOperationException auslösen.

Bearbeiten:

Die anderen sprechen einen interessanten Punkt an, der nicht unterstützt wird. Meine einzige Sorge bei einer NotSupportedException ist, dass dies im Allgemeinen die Ausnahmen sind, die ausgelöst werden, wenn "dunkle Materie" in das System eingeführt wurde, oder anders ausgedrückt: "Diese Methode muss auf dieser Schnittstelle in das System eingegeben werden, aber wir haben gewonnen." erst in Version 2.4 einschalten "

Ich habe auch gesehen, dass NotSupportedExceptions als Lizenzausnahme ausgelöst werden. "Sie führen die kostenlose Version dieser Software aus, diese Funktion wird nicht unterstützt."

Bearbeiten 2:

Ein weiterer möglicher:

System.ComponentModel.InvalidEnumArgumentException  

Die Ausnahme, die ausgelöst wird, wenn ungültige Argumente verwendet werden, die Enumeratoren sind.

Peter
quelle
Ich werde es auf eine Aufzählung beschränken müssen (nach einigem Hin und Her) - es sind nur die Flaggen, um die ich mir Sorgen mache.
Jon Skeet
Ich denke, diese Lizenzgeber sollten eine Instanz einer LicensingExceptionKlasse werfen, von der sie erben InvalidOperationException.
Mehrdad Afshari
Ich stimme Mehrdad zu. Ausnahmen sind leider einer der Bereiche, in denen das Framework viel Grau enthält. Aber ich bin sicher, dass dies für viele Sprachen gleich ist. (ohne zu sagen, ich würde zu vb6s Laufzeitfehler 13 zurückkehren, hehe)
Peter