Perché C # ti consente di "lanciare null"?


85

Durante la scrittura di un codice particolarmente complesso per la gestione delle eccezioni, qualcuno ha chiesto, non è necessario assicurarsi che l'oggetto eccezione non sia nullo? E ho detto, ovviamente no, ma poi ho deciso di provarlo. Apparentemente, puoi lanciare null, ma è ancora trasformato in un'eccezione da qualche parte.

Perché è permesso?

throw null;

In questo frammento, per fortuna "ex" non è nullo, ma potrebbe mai esserlo?

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

  //thankfully, it isn't null, but is
  //ex is System.NullReferenceException
}

9
Sei sicuro di non vedere un'eccezione .NET (riferimento nullo) generata sopra il tuo lancio?
micahtan

4
Si potrebbe pensare che il compilatore lanci almeno un avvertimento sul tentativo di "lanciare null" direttamente;
Andy White

No, non sono sicuro. Il framework potrebbe benissimo provare a fare qualcosa con il mio oggetto e quando è nullo, il framework genera una "eccezione di riferimento nullo" .. ma alla fine, voglio essere sicuro che "ex" non possa mai essere nullo
scherzo

1
@ Andy: un avvertimento può avere senso per altre situazioni del linguaggio, ma per throwun'affermazione il cui scopo è di lanciare un'eccezione in primo luogo, un avvertimento non aggiunge molto valore.
mmx

@ Mehrdad - sì, ma dubito che uno sviluppatore intenzionalmente "lancia un null" e vuole vedere un'eccezione NullReferenceException. Forse dovrebbe essere un errore di compilazione completo, come un confronto = errato in un'istruzione if.
Andy White

Risposte:


96

Perché la specifica del linguaggio richiede un'espressione di tipo System.Exception(quindi nullè valida in quel contesto) e non limita questa espressione a non essere nulla. In generale, non è possibile rilevare se il valore di quell'espressione è nullo meno. Dovrebbe risolvere il problema dell'arresto. Il runtime dovrà nullcomunque occuparsi del caso. Vedere:

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

Potrebbero, ovviamente, rendere nullinvalido il caso specifico di gettare il letterale, ma ciò non aiuterebbe molto, quindi perché sprecare lo spazio delle specifiche e ridurre la coerenza con poco vantaggio?

Disclaimer (prima di essere schiaffeggiato da Eric Lippert): questa è la mia speculazione sul ragionamento alla base di questa decisione progettuale. Ovviamente non ho partecipato alla riunione di progettazione;)


La risposta alla tua seconda domanda, se una variabile di espressione catturata all'interno di una clausola catch può mai essere nulla: mentre la specifica C # nullnon dice se altri linguaggi possono causare la propagazione di un'eccezione, definisce il modo in cui vengono propagate le eccezioni:

Le clausole catch, se presenti, vengono esaminate in ordine di apparizione per individuare un gestore adatto per l'eccezione. La prima clausola catch che specifica il tipo di eccezione o un tipo di base del tipo di eccezione è considerata una corrispondenza. Una clausola catch generale è considerata una corrispondenza per qualsiasi tipo di eccezione. [...]

Perché nulll'affermazione in grassetto è falsa. Quindi, sebbene basandosi esclusivamente su ciò che dice la specifica C #, non possiamo dire che il runtime sottostante non genererà mai null, possiamo essere sicuri che anche se questo è il caso, sarà gestito solo dalla catch {}clausola generica .

Per le implementazioni C # sulla CLI, possiamo fare riferimento alla specifica ECMA 335. Quel documento definisce tutte le eccezioni che la CLI genera internamente (nessuna delle quali lo è null) e menziona che gli oggetti eccezione definiti dall'utente vengono lanciati throwdall'istruzione. La descrizione di tale istruzione è praticamente identica all'istruzione C # throw(tranne per il fatto che non limita il tipo di oggetto a System.Exception):

Descrizione:

L' throwistruzione lancia l'oggetto eccezione (tipo O) sullo stack e svuota lo stack. Per i dettagli sul meccanismo di eccezione, vedere la partizione I.
[Nota: sebbene la CLI consenta il lancio di qualsiasi oggetto, la CLS descrive una classe di eccezione specifica che deve essere utilizzata per l'interoperabilità del linguaggio. nota finale]

Eccezioni:

System.NullReferenceExceptionviene lanciato se objè null.

Correttezza:

Il CIL corretto garantisce che l'oggetto sia sempre nullo un riferimento a un oggetto (cioè di tipo O).

Credo che questi siano sufficienti per concludere che le eccezioni colte non lo sono mai null.


Suppongo che il meglio che potrebbe fare sarebbe proibire frasi esplicite come throw null;.
FrustratedWithFormsDesigner

7
In realtà, è del tutto possibile (nel CLR) che venga lanciato un oggetto che non eredita da System.Exception. Non puoi farlo in C # per quanto ne so, ma può essere fatto tramite IL, C ++ / CLR, ecc. Vedi msdn.microsoft.com/en-us/library/ms404228.aspx per ulteriori informazioni.
tecnofilo

@ tecnophile: Sì. Sto parlando della lingua qui. In C # non è possibile generare un'eccezione di altri tipi, ma è possibile rilevarla utilizzando una catch { }clausola generica .
mmx

1
Anche se non avrebbe senso per un compilatore rifiutarsi di accettare un throwargomento il cui argomento potrebbe essere un valore nullo, ciò non implicherebbe che throw nulldovrebbe essere legale. Un compilatore potrebbe insistere sul fatto che un throwargomento ha un tipo di classe distinguibile. Un'espressione come (System.InvalidOperationException)nulldovrebbe essere valida in fase di compilazione (eseguirla dovrebbe causare a NullReferenceException) ma ciò non significa che un untyped nulldovrebbe essere accettabile.
supercat

@supercat: è vero, ma il null in questo caso è implicitamente digitato come Exception (perché è utilizzato in un contesto in cui è richiesta Exception). null non è valido in fase di esecuzione, quindi viene generata NullReferenceException (come indicato nella risposta di Anon.). L'eccezione generata non è quella nell'istruzione "throw".
John B. Lambe

29

Apparentemente, puoi lanciare null, ma è ancora trasformato in un'eccezione da qualche parte.

Il tentativo di lanciare un nulloggetto produce un'eccezione di riferimento nullo (completamente non correlata).

Chiedere perché ti è permesso lanciare nullè come chiedere perché ti è permesso farlo:

object o = null;
o.ToString();

5

Tratto da qui :

Se usi questa espressione nel codice C #, verrà generata un'eccezione NullReferenceException. Questo perché l'istruzione throw richiede un oggetto di tipo Exception come singolo parametro. Ma questo stesso oggetto è nullo nel mio esempio.


5

Anche se potrebbe non essere possibile lanciare null in C # perché il lancio lo rileverà e lo trasformerà in un'eccezione NullReferenceException, È possibile ricevere null ... Mi capita di riceverlo proprio ora, il che causa la mia cattura (che non era aspettandosi che 'ex' sia nullo) per sperimentare un'eccezione di riferimento nullo che quindi si traduce nella morte della mia app (poiché quella era l'ultima cattura).

Quindi, anche se non possiamo lanciare null da C #, il netherworld può lanciare null, quindi la tua cattura più esterna (eccezione ex) è meglio essere preparati a riceverla. Solo FYI.


3
Interessante. Potresti pubblicare del codice o forse eludere ciò che si trova nella tua profondità sotto che sta facendo questo?
Peter Lillevold

Non posso dirti se si tratta di un bug di CLR ... è difficile rintracciare cosa nelle viscere di .NET ha gettato un valore nullo e perché dato che non si ottiene un'eccezione con stack di chiamate o altri indizi per andare avanti. So solo che la mia cattura più esterna ora controlla l'argomento eccezione nullo prima di usarlo.
Brian Kennedy

3
Sospetto che sia un bug di CLR o dovuto a un codice non verificabile non valido. Il CIL corretto genererà solo un oggetto non nullo o nullo (che diventa una NullReferenceException).
Demi

Sto anche utilizzando un servizio che restituisce un'eccezione nulla. Hai mai capito come veniva lanciata l'eccezione nulla?
ilmiDdlest

2

Penso che forse non puoi - quando provi a lanciare null, non può, quindi fa quello che dovrebbe in un caso di errore, che è un'eccezione di riferimento null. Quindi non stai effettivamente lanciando il null, non stai riuscendo a lanciare il null, il che si traduce in un lancio.


2

Cercando di rispondere "..per fortuna 'ex' non è nullo, ma potrebbe mai esserlo?":

Poiché probabilmente non possiamo generare eccezioni nulle, una clausola catch non dovrà mai rilevare un'eccezione nullo. Quindi, ex non potrebbe mai essere nullo.

Vedo ora che questa domanda in effetti è già stata posta .


@Brian Kennedy ci dice nel suo commento sopra che possiamo prendere null.
ProfK

@ Tvde1 - Penso che la distinzione qui sia che, sì, puoi eseguire throw null. Ma ciò non genererà un'eccezione nulla . Piuttosto, poiché throw nulltenta di invocare metodi su un riferimento null, ciò costringe successivamente il runtime a generare un'istanza non nulla di NullReferenceException.
Peter Lillevold

1

Ricorda che un'eccezione include dettagli su dove viene generata l'eccezione. Visto che il costruttore non ha idea di dove debba essere lanciato, ha senso solo che il metodo throw inietti quei dettagli nell'oggetto nel punto in cui si trova il lancio. In altre parole, il CLR sta tentando di iniettare dati in null, il che attiva un'eccezione NullReferenceException.

Non sono sicuro che sia esattamente ciò che sta accadendo, ma spiega il fenomeno.

Supponendo che questo sia vero (e non riesco a pensare a un modo migliore per far sì che ex sia nullo rispetto a lanciare null;), ciò significherebbe che ex non può essere nullo.


-1

Nelle versioni precedenti di c #:

Considera questa sintassi:

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

Penso che sia molto più breve di questo:

public void Add<T> ( T item )
{
    if (!hashSet.Add ( item ))
        throw new Exception ( "The item already exists" );
}

Cosa ci dice questo?
bornfromanegg

Per inciso, non sono sicuro di quale sia la tua definizione di più breve, ma il tuo secondo esempio contiene meno caratteri.
bornfromanegg

@bornfromanegg no il primo è inline, quindi è decisamente più corto. Quello che stavo cercando di dire è che puoi generare un errore a seconda delle condizioni. Tradizionalmente, si preferisce il secondo esempio, ma poiché "lancia null" non genera nulla, è possibile incorporare il secondo esempio in modo che assomigli al primo esempio. Inoltre, il 1 ° esempio ha 8 caratteri in meno del 2 °
Arutyun Enfendzhyan

"Throw null" genera un'eccezione NullReference. Il che significa che il tuo primo esempio genererà sempre un'eccezione.
bornfromanegg

sì, sfortunatamente lo fa ora. Ma prima non era così
Arutyun Enfendzhyan
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.