Warum erlaubt C # Ihnen, Null zu werfen?

84

Beim Schreiben eines besonders komplexen Codes für die Ausnahmebehandlung hat jemand gefragt, ob Sie nicht sicherstellen müssen, dass Ihr Ausnahmeobjekt nicht null ist. Und ich sagte natürlich nicht, entschied mich dann aber, es zu versuchen. Anscheinend können Sie null werfen, aber es wird immer noch irgendwo zu einer Ausnahme.

Warum ist das erlaubt?

throw null;

In diesem Ausschnitt ist 'ex' zum Glück nicht null, aber könnte es jemals sein?

try
{
  throw null;
}
catch (Exception ex)
{
  //can ex ever be null?

  //thankfully, it isn't null, but is
  //ex is System.NullReferenceException
}
witzeln
quelle
8
Sind Sie sicher, dass keine .NET-Ausnahme (Nullreferenz) über Ihrem eigenen Wurf angezeigt wird?
Micahtan
4
Sie würden denken, der Compiler würde zumindest eine Warnung vor dem Versuch auslösen, direkt "null zu werfen";
Andy White
Nein, ich bin mir nicht sicher. Das Framework könnte sehr gut versuchen, etwas mit meinem Objekt zu tun, und wenn es null ist, löst das Framework eine "Null-Referenz-Ausnahme" aus. Letztendlich möchte ich jedoch sicher sein, dass "ex" niemals null sein kann
witzeln Sie
1
@Andy: Eine Warnung kann für andere Situationen in der Sprache sinnvoll sein, aber für throweine Anweisung, deren Zweck darin besteht, überhaupt eine Ausnahme auszulösen , bringt eine Warnung nicht viel Wert.
Mehrdad Afshari
@Mehrdad - ja, aber ich bezweifle, dass ein Entwickler absichtlich "null werfen" würde und eine NullReferenceException sehen möchte. Vielleicht sollte es ein vollständiger Build-Fehler sein, wie ein falscher = Vergleich in einer if-Anweisung.
Andy White

Antworten:

94

Da die Sprachspezifikation dort einen Ausdruck vom Typ erwartet System.Exception(daher nullin diesem Kontext gültig ist) und diesen Ausdruck nicht auf Nicht-Null beschränkt. Im Allgemeinen kann nicht festgestellt werden, ob der Wert dieses Ausdrucks ist nulloder nicht. Es müsste das Halteproblem lösen. Die Laufzeit muss sich nullsowieso mit dem Fall befassen . Sehen:

Exception ex = null;
if (conditionThatDependsOnSomeInput) 
    ex = new Exception();
throw ex; 

Sie könnten natürlich den speziellen Fall, das nullLiteral ungültig zu machen, ungültig machen, aber das würde nicht viel helfen. Warum also Spezifikationsraum verschwenden und die Konsistenz für wenig Nutzen reduzieren?

Haftungsausschluss (bevor ich von Eric Lippert geschlagen werde): Dies ist meine eigene Spekulation über die Gründe für diese Designentscheidung. Natürlich war ich nicht im Designtreffen;)


Die Antwort auf Ihre zweite Frage, ob eine in einer catch-Klausel abgefangene Ausdrucksvariable jemals null sein kann: Während die C # -Spezifikation nullkeine Aussage darüber enthält, ob andere Sprachen die Weitergabe einer Ausnahme verursachen können , definiert sie die Art und Weise, wie Ausnahmen weitergegeben werden:

Die etwaigen catch-Klauseln werden in der Reihenfolge ihres Auftretens geprüft, um einen geeigneten Handler für die Ausnahme zu finden. Die erste catch-Klausel , die den Ausnahmetyp oder einen Basistyp des Ausnahmetyps angibt, wird als Übereinstimmung betrachtet. Eine allgemeine catch-Klausel gilt als Übereinstimmung für jeden Ausnahmetyp. [...]

Denn nulldie fette Aussage ist falsch. Obwohl dies nur auf den Aussagen der C # -Spezifikation basiert, können wir nicht sagen, dass die zugrunde liegende Laufzeit niemals null auslösen wird. Wir können jedoch sicher sein, dass selbst wenn dies der Fall ist, dies nur von der generischen catch {}Klausel behandelt wird.

Für C # -Implementierungen auf der CLI können wir auf die ECMA 335-Spezifikation verweisen. In diesem Dokument werden alle Ausnahmen definiert, die die CLI intern auslöst (keine davon null), und es wird erwähnt, dass benutzerdefinierte Ausnahmeobjekte von der throwAnweisung ausgelöst werden . Die Beschreibung für diese Anweisung ist praktisch identisch mit der C # throw-Anweisung (außer dass sie den Typ des Objekts nicht auf Folgendes beschränkt System.Exception):

Beschreibung:

Die throwAnweisung wirft das Ausnahmeobjekt (Typ O) auf den Stapel und leert den Stapel. Einzelheiten zum Ausnahmemechanismus finden Sie unter Partition I.
[Hinweis: Während die CLI das Auslösen von Objekten zulässt, beschreibt die CLS eine bestimmte Ausnahmeklasse, die für die Sprachinteroperabilität verwendet werden soll. Endnote]

Ausnahmen:

System.NullReferenceExceptionwird geworfen wenn objist null.

Richtigkeit:

Durch die korrekte CIL wird sichergestellt, dass das Objekt immer entweder nulloder eine Objektreferenz (dh vom Typ O) ist.

Ich glaube, diese reichen aus, um zu dem Schluss zu kommen, dass es keine Ausnahmen gibt null.

Mehrdad Afshari
quelle
Ich nehme an, das Beste, was es tun könnte, wäre, explizite Sätze wie zu verbieten throw null;.
FrustratedWithFormsDesigner
7
Tatsächlich ist es durchaus möglich (in der CLR), ein Objekt zu werfen, das nicht von System.Exception erbt. Soweit ich weiß, können Sie dies nicht in C # tun, aber es kann über IL, C ++ / CLR usw. erfolgen. Weitere Informationen finden Sie unter msdn.microsoft.com/en-us/library/ms404228.aspx .
Technophile
@technophile: Ja. Ich spreche hier über die Sprache. In C # ist es nicht möglich, eine Ausnahme von anderen Typen auszulösen, aber es ist möglich, sie mit einer generischen catch { }Klausel abzufangen.
Mehrdad Afshari
1
Während es für einen Compiler keinen Sinn machen würde, sich zu weigern, einen zu akzeptieren, throwdessen Argument ein Nullwert sein könnte, würde dies nicht bedeuten, dass throw nulldies legal sein müsste. Ein Compiler könnte darauf bestehen, dass ein throwArgument einen erkennbaren Klassentyp hat. Ein Ausdruck wie (System.InvalidOperationException)nullsollte zur Kompilierungszeit gültig sein (das Ausführen sollte a verursachen NullReferenceException), aber das bedeutet nicht, dass ein untypisierter Ausdruck nullakzeptabel sein sollte.
Supercat
@supercat: Das stimmt, aber die Null wird in diesem Fall implizit als Ausnahme eingegeben (da sie in einem Kontext verwendet wird, in dem eine Ausnahme erforderlich ist). null ist zur Laufzeit nicht gültig, daher wird die NullReferenceException ausgelöst (wie in der Antwort von Anon angegeben). Die ausgelöste Ausnahme ist nicht die in der 'throw'-Anweisung.
John B. Lambe
29

Anscheinend können Sie null werfen, aber es wird immer noch irgendwo zu einer Ausnahme.

Der Versuch, ein nullObjekt zu werfen, führt zu einer (völlig unabhängigen) Nullreferenzausnahme.

Zu fragen, warum du werfen darfst, nullist wie zu fragen, warum du das tun darfst:

object o = null;
o.ToString();
Anon.
quelle
5

Von hier genommen :

Wenn Sie diesen Ausdruck in Ihrem C # -Code verwenden, wird eine NullReferenceException ausgelöst. Dies liegt daran, dass die throw-Anweisung ein Objekt vom Typ Exception als einzigen Parameter benötigt. Aber genau dieses Objekt ist in meinem Beispiel null.

Fitzchak Yitzchaki
quelle
5

Während es möglicherweise nicht möglich ist, null in C # zu werfen, weil der Wurf dies erkennt und es in eine NullReferenceException verwandelt, ist es möglich, null zu empfangen ... Ich erhalte das gerade, was meinen Fang verursacht (was nicht der Fall war) erwartet, dass 'ex' null ist), um eine Nullreferenzausnahme zu erfahren, die dann dazu führt, dass meine App stirbt (da dies der letzte Haken war).

Während wir also keine Null aus C # werfen können, kann die Unterwelt null werfen, sodass Ihr äußerster Fang (Ausnahme ex) besser darauf vorbereitet ist, ihn zu empfangen. Nur zur Info.

Brian Kennedy
quelle
3
Interessant. Könnten Sie einen Code posten oder sich vielleicht dem entziehen, was sich in Ihrer Tiefe darunter befindet, um dies zu tun?
Peter Lillevold
Ich kann Ihnen nicht sagen, ob es sich um einen CLR-Fehler handelt. Es ist schwer herauszufinden, was in den Eingeweiden von .NET null geworfen hat und warum Sie keine Ausnahme mit Aufrufstapel oder anderen Hinweisen erhalten. Ich weiß nur, dass mein äußerster Fang jetzt vor der Verwendung auf null Exception-Argumente prüft.
Brian Kennedy
3
Ich vermute, dass es sich um einen CLR-Fehler handelt oder um einen schlechten, nicht überprüfbaren Code. Bei korrekter CIL wird nur entweder ein Nicht-Null-Objekt oder Null (was zu einer NullReferenceException wird) ausgelöst.
Demi
Ich verwende auch einen Dienst, der eine Null-Ausnahme zurückgibt. Haben Sie jemals herausgefunden, wie die Null-Ausnahme ausgelöst wurde?
themiDdlest
2

Ich denke, vielleicht können Sie nicht - wenn Sie versuchen, null zu werfen, kann es nicht, also tut es, was es sollte, in einem Fehlerfall, der eine Nullreferenzausnahme auslöst. Sie werfen also nicht die Null, sondern die Null nicht, was zu einem Wurf führt.

Steve Cooper
quelle
2

Der Versuch zu antworten "..dankbar 'ex' ist nicht null, aber könnte es jemals sein?":

Da wir wohl keine Ausnahmen auslösen können, die null sind, muss eine catch-Klausel auch niemals eine Ausnahme abfangen, die null ist. Somit könnte ex niemals null sein.

Ich sehe jetzt , dass diese Frage in der Tat wurde bereits gefragt .

Peter Lillevold
quelle
@ Brian Kennedy sagt uns in seinem Kommentar oben, dass wir null fangen können.
ProfK
@ Tvde1 - Ich denke, der Unterschied hier ist, dass Sie ja ausführen können throw null. Dies löst jedoch keine Ausnahme aus, die null ist . Da throw nullversucht wird, Methoden für eine Nullreferenz aufzurufen, wird die Laufzeit anschließend gezwungen, eine Nicht-Null-Instanz von auszulösen NullReferenceException.
Peter Lillevold
1

Denken Sie daran, dass eine Ausnahme Details darüber enthält, wo die Ausnahme ausgelöst wird. Da der Konstruktor keine Ahnung hat, wohin er geworfen werden soll, ist es nur sinnvoll, dass die throw-Methode diese Details an dem Punkt in das Objekt einfügt, an dem der Wurf erfolgt. Mit anderen Worten, die CLR versucht, Daten in null einzufügen, wodurch eine NullReferenceException ausgelöst wird.

Ich bin mir nicht sicher, ob genau das passiert, aber es erklärt das Phänomen.

Angenommen, dies ist wahr (und ich kann mir keinen besseren Weg vorstellen, um ex auf null zu bringen, als null zu werfen;), würde dies bedeuten, dass ex unmöglich null sein kann.

Guvante
quelle
0

In älteren c #:

Betrachten Sie diese Syntax:

public void Add<T> ( T item ) => throw (hashSet.Add ( item ) ? null : new Exception ( "The item already exists" ));

Ich denke, es ist viel kürzer als das:

public void Add<T> ( T item )
{
    if (!hashSet.Add ( item ))
        throw new Exception ( "The item already exists" );
}
Arutyun Enfendzhyan
quelle
Was sagt uns das?
geboren
Ich bin mir übrigens nicht sicher, wie Sie kürzer definieren, aber Ihr zweites Beispiel enthält weniger Zeichen.
geboren
@bornfromanegg nein der 1. ist inline, also definitiv kürzer. Was ich versucht habe zu sagen ist, dass Sie einen Fehler abhängig von der Bedingung werfen können. Traditionell möchten Sie das zweite Beispiel, aber da "throw null" nichts auslöst, können Sie das zweite Beispiel inline setzen, um wie das erste Beispiel auszusehen. Das erste Beispiel hat 8 Zeichen weniger als das zweite
Arutyun Enfendzhyan
"Throw null" löst eine NullReference-Ausnahme aus. Das heißt, Ihr erstes Beispiel löst immer eine Ausnahme aus.
geboren
ja leider tut es jetzt. Aber vorher nicht
Arutyun Enfendzhyan