Por que o C # permite que você 'lance nulo'?

85

Ao escrever algum código de tratamento de exceção particularmente complexo, alguém perguntou, você não precisa ter certeza de que seu objeto de exceção não é nulo? E eu disse claro que não, mas resolvi tentar. Aparentemente, você pode lançar null, mas ainda é uma exceção em algum lugar.

Por que isso é permitido?

throw null;

Neste trecho, felizmente 'ex' não é nulo, mas poderia ser?

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

  //thankfully, it isn't null, but is
  //ex is System.NullReferenceException
}
gracejo
fonte
8
Tem certeza de que não está vendo uma exceção .NET (referência nula) lançada em cima do seu próprio lançamento?
micahtan
4
Você pensaria que o compilador pelo menos geraria um aviso sobre a tentativa de "lançar null" diretamente;
Andy White
Não, não tenho certeza. O framework pode muito bem estar tentando fazer algo com meu objeto e quando ele é nulo, o framework lança uma "Exceção de referência nula" .. mas no final das contas, eu quero ter certeza de que "ex" nunca pode ser nulo
gracejo
1
@Andy: Um aviso pode fazer sentido para outras situações no idioma, mas para throwuma declaração cujo objetivo é lançar uma exceção em primeiro lugar, um aviso não agrega muito valor.
mmx
@Mehrdad - sim, mas duvido que algum desenvolvedor propositalmente "jogue null" e queira ver um NullReferenceException. Talvez devesse ser um erro de compilação completo, como uma comparação incorreta = em uma instrução if.
Andy White

Respostas:

96

Porque a especificação da linguagem espera uma expressão do tipo System.Exceptionlá (portanto, nullé válida nesse contexto) e não restringe essa expressão a ser não nula. Em geral, não há como detectar se o valor dessa expressão é nullou não. Teria de resolver o problema da parada. O tempo de execução terá que lidar com o nullcaso de qualquer maneira. Vejo:

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

Eles poderiam, é claro, fazer o caso específico de jogar o nullliteral inválido, mas isso não ajudaria muito, então por que desperdiçar espaço de especificação e reduzir a consistência para obter poucos benefícios?

Isenção de responsabilidade (antes de eu levar um tapa de Eric Lippert): Esta é minha própria especulação sobre o raciocínio por trás dessa decisão de design. Claro, eu não estive na reunião de design;)


A resposta à sua segunda pergunta, se uma variável de expressão capturada dentro de uma cláusula catch pode ser nula: Embora a especificação C # não diga se outras linguagens podem causar nulla propagação de uma exceção, ela define a maneira como as exceções são propagadas:

As cláusulas catch, se houver, são examinadas em ordem de aparecimento para localizar um manipulador adequado para a exceção. A primeira cláusula catch que especifica o tipo de exceção ou um tipo base do tipo de exceção é considerada uma correspondência. Uma cláusula catch geral é considerada uma correspondência para qualquer tipo de exceção. [...]

Pois null, a afirmação em negrito é falsa. Portanto, embora seja puramente baseado no que a especificação do C # diz, não podemos dizer que o tempo de execução subjacente nunca lançará nulo, podemos ter certeza de que, mesmo se for esse o caso, ele será tratado apenas pela catch {}cláusula genérica .

Para implementações C # na CLI, podemos consultar a especificação ECMA 335. Esse documento define todas as exceções que a CLI lança internamente (nenhuma das quais são null) e menciona que os objetos de exceção definidos pelo usuário são lançados pela throwinstrução. A descrição dessa instrução é virtualmente idêntica à throwinstrução C # (exceto que não restringe o tipo do objeto a System.Exception):

Descrição:

A throwinstrução lança o objeto de exceção (tipo O) na pilha e esvazia a pilha. Para obter detalhes sobre o mecanismo de exceção, consulte a Partição I.
[Nota: Enquanto o CLI permite que qualquer objeto seja lançado, o CLS descreve uma classe de exceção específica que deve ser usada para interoperabilidade de linguagem. nota final]

Exceções:

System.NullReferenceExceptioné lançado se objfor null.

Correção:

O CIL correto garante que o objeto seja sempre nullou uma referência de objeto (ou seja, do tipo O).

Acredito que isso seja suficiente para concluir que as exceções detectadas nunca são null.

mmx
fonte
Suponho que o melhor que poderia fazer seria proibir frases explícitas como throw null;.
FrustratedWithFormsDesigner
7
Na verdade, é perfeitamente possível (no CLR) que seja lançado um objeto que não herde de System.Exception. Você não pode fazer isso em C # até onde eu sei, mas pode ser feito via IL, C ++ / CLR, etc. Consulte msdn.microsoft.com/en-us/library/ms404228.aspx para obter mais informações.
tecnófilo
@technophile: Sim. Estou falando sobre a linguagem aqui. Em C #, não é possível lançar uma exceção de outros tipos, mas é possível capturá-la usando uma catch { }cláusula genérica .
mmx
1
Embora não faça sentido para um compilador se recusar a aceitar um throwcujo argumento possa ser um valor nulo, isso não implicaria que throw nullisso teria que ser legal. Um compilador pode insistir que um throwargumento tenha um tipo de classe discernível. Uma expressão like (System.InvalidOperationException)nulldeve ser válida em tempo de compilação (executá-la deve causar a NullReferenceException), mas isso não significa que um não tipado nulldeve ser aceitável.
supercat
@supercat: Isso é verdade, mas o nulo, neste caso, é implicitamente tipado como Exceção (porque é usado em um contexto onde a Exceção é necessária). null não é válido em tempo de execução, então NullReferenceException é lançada (conforme declarado na resposta de Anon.). A exceção lançada não é aquela na instrução 'throw'.
John B. Lambe
29

Aparentemente, você pode lançar null, mas ainda é uma exceção em algum lugar.

A tentativa de lançar um nullobjeto resulta em uma exceção de referência nula (completamente não relacionada).

Perguntar por que você tem permissão para jogar nullé como perguntar por que você tem permissão para fazer isso:

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

Retirado daqui :

Se você usar essa expressão em seu código C #, ela lançará uma NullReferenceException. Isso ocorre porque a instrução throw precisa de um objeto do tipo Exception como seu único parâmetro. Mas esse mesmo objeto é nulo em meu exemplo.

Fitzchak Yitzchaki
fonte
5

Embora não seja possível lançar null em C # porque o lançamento irá detectar isso e transformá-lo em um NullReferenceException, É possível receber null ... Acontece que estou recebendo isso agora, o que causa minha captura (que não era esperando que 'ex' seja nulo) para experimentar uma exceção de referência nula que, em seguida, resulta na morte do meu aplicativo (já que essa foi a última captura).

Portanto, embora não possamos lançar null a partir do C #, o submundo pode lançar null, então é melhor que sua captura mais externa (Exceção) esteja preparado para recebê-la. Apenas para sua informação.

Brian Kennedy
fonte
3
Interessante. Você poderia postar algum código ou talvez iludir o que está em suas profundezas abaixo que está fazendo isso?
Peter Lillevold
Não sei dizer se é um bug de CLR ... é difícil rastrear o que no interior do .NET gerou null e por que, dado que você não obtém uma exceção com pilha de chamadas ou qualquer outra pista para continuar. Eu só sei que minha captura mais externa agora verifica o argumento de exceção nulo antes de usá-lo.
Brian Kennedy,
3
Suspeito que seja um bug do CLR ou devido a um código incorreto e não verificável. CIL correto lançará apenas um objeto não nulo ou nulo (que se torna uma NullReferenceException).
Demi
Também estou usando um serviço que está retornando uma exceção nula. Você já descobriu como a exceção nula estava sendo lançada?
themiDdlest
2

Eu acho que talvez você não possa - quando você tenta lançar null, ele não consegue, então ele faz o que deveria em um caso de erro, que é lançar uma exceção de referência nula. Portanto, você não está realmente lançando o nulo, você está falhando em lançar o nulo, o que resulta em um lançamento.

Steve Cooper
fonte
2

Tentando responder "..a gratidão 'ex' não é nulo, mas poderia ser?":

Uma vez que, sem dúvida, não podemos lançar exceções nulas, uma cláusula catch também nunca terá que capturar uma exceção nula. Portanto, ex nunca poderia ser nulo.

Vejo agora que essa pergunta de fato já foi feita .

Peter Lillevold
fonte
@Brian Kennedy nos diz em seu comentário acima que podemos pegar null.
ProfK de
@ Tvde1 - Acho que a diferença aqui é que, sim, você pode executar throw null. Mas isso não lançará uma exceção nula . Em vez disso, como throw nulltenta invocar métodos em uma referência nula, isso subsequentemente força o tempo de execução a lançar uma instância não nula de NullReferenceException.
Peter Lillevold
1

Lembre-se de que uma exceção inclui detalhes sobre onde a exceção é lançada. Visto que o construtor não tem ideia de onde deve ser lançado, então só faz sentido que o método de lançamento injete esses detalhes no objeto no ponto em que o lançamento está. Em outras palavras, o CLR está tentando injetar dados em nulos, o que dispara uma NullReferenceException.

Não tenho certeza se isso é exatamente o que está acontecendo, mas explica o fenômeno.

Supondo que isso seja verdade (e não consigo imaginar uma maneira melhor de fazer com que ex seja nulo do que lançar nulo;), isso significaria que ex não pode ser nulo.

Guvante
fonte
0

No antigo c #:

Considere esta sintaxe:

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

Acho que é bem mais curto do que isso:

public void Add<T> ( T item )
{
    if (!hashSet.Add ( item ))
        throw new Exception ( "The item already exists" );
}
Arutyun Enfendzhyan
fonte
O que isso nos diz?
bornfromanegg
A propósito, não tenho certeza de qual é sua definição de mais curto, mas seu segundo exemplo contém menos caracteres.
bornfromanegg
@bornfromanegg não, o primeiro é embutido, então é definitivamente mais curto. O que eu estava tentando dizer é que você pode lançar um erro dependendo da condição. Tradicionalmente, você faria como o segundo exemplo, mas como "lançar null" não lança nada, você pode embutir o segundo exemplo para se parecer com o primeiro exemplo. Além disso, o primeiro exemplo tem 8 caracteres a menos que o segundo
Arutyun Enfendzhyan de
“Throw null” lança uma exceção NullReference. O que significa que seu primeiro exemplo sempre lançará uma exceção.
bornfromanegg
sim, infelizmente agora. Mas isso não acontecia antes
Arutyun Enfendzhyan