Referência de objeto não definida para uma instância de um objeto. Por que o .NET não mostra qual objeto é `nulo`?

103

Em relação a esta mensagem de exceção não tratada do .NET:

Referência de objeto não definida para uma instância de um objeto.

Por que o .NET não mostra qual objeto é null?

Sei que posso verificar nulle resolver o erro. No entanto, por que o .NET não ajuda a apontar qual objeto tem uma referência nula e qual expressão acionou o NullReferenceException?


fonte
2
Quando isso acontecer, reescreva a linha em que aconteceu para que verifique cada resultado possível para nulo primeiro - então você saberá exatamente o que era. Ou isso, ou ter o depurador incrível do Visual Studio anexado, que interrompe o instante em que uma exceção ocorre e permite que você veja o que é nulo :)
Patashu
5
Na verdade não, ele simplesmente pergunta por que o framework .NET não ajuda o programador a mostrar qual objeto é nulo. Eu acho que é a penalidade de desempenho (você precisa de reflexão). mas também não tenho certeza.
bas
1
@bas: Embora isso seja verdade, a pergunta é um pouco enganosa, pois deveria ser sobre uma "parte de uma expressão", não sobre um "objeto". Isso também explica por que a mera reflexão não ajuda, mas algumas informações de depuração extensas serão necessárias.
OR Mapper
4
Ainda estou curioso para a resposta. É algo semelhante às exceções .net, não ajudando a apontar qual chave não existe em um dicionário. Além disso, não entendo os devotos sobre a questão.
bas
12
Terminologia, por favor: Um objeto nunca é nulo. Uma referência de objeto pode ser embora. Mas uma referência de objeto é apenas um local na memória - como isso o ajudaria, a menos que você tenha um depurador anexado de qualquer maneira?
Oskar Berggren

Respostas:

169

(Para obter informações sobre o novo auxiliar de exceção no Visual Studio 2017, consulte o final desta resposta)


Considere este código:

String s = null;
Console.WriteLine(s.Length);

Isso lançará um NullReferenceExceptionna segunda linha e você deseja saber por que o .NET não informa sque era nulo quando a exceção foi lançada.

Para entender por que você não consegue essa informação, você deve se lembrar de que não é a fonte C # que executa, mas IL:

IL_0001: ldnull      
IL_0002: stloc.0 // s
IL_0003: ldloc.0 // s
IL_0004: callvirt System.String.get_Length
IL_0009: chame System.Console.WriteLine

É o callvirtopcode que lança o NullReferenceExceptione faz isso quando o primeiro argumento na pilha de avaliação é uma referência nula (aquela que foi carregada usando ldloc.0).

Se o .NET for capaz de dizer que se trata de suma referência nula, ele deve, de alguma forma, rastrear que o primeiro argumento na pilha de avaliação se originou s. Nesse caso, é fácil para nós ver que é snulo, mas e se o valor for um valor de retorno de outra chamada de função e não estiver armazenado em nenhuma variável? De qualquer forma, esse tipo de informação não é o que você deseja acompanhar em uma máquina virtual como a máquina virtual .NET.


Para evitar esse problema, sugiro que você execute a verificação de argumento nulo em todas as chamadas de método público (a menos, é claro, que você permita a referência nula):

public void Foo(String s) {
  if (s == null)
    throw new ArgumentNullException("s");
  Console.WriteLine(s.Length);
}

Se null for passado para o método, você obterá uma exceção que descreve precisamente qual é o problema (isto sé, null).


Quatro anos depois, o Visual Studio 2017 agora tem um novo auxiliar de exceção que tentará dizer o que é nulo quando um NullReferenceExceptioné lançado. Ele pode até mesmo fornecer as informações necessárias quando for o valor de retorno de um método nulo:

Auxiliar de exceção do Visual Studio 2017

Observe que isso só funciona em um build DEBUG.

Martin Liversage
fonte
5
Os números de linha e os nomes dos arquivos de origem também não são armazenados no código IL, ou estão? Ainda assim, eles podem ser disponibilizados para depuração.
OR Mapper
4
@MartinLiversage: Exatamente. Portanto, a questão se resume a: por que não há informações suficientes armazenadas nos arquivos de símbolos que também informam para qual expressão do código foi avaliada null.
OR Mapper
2
@MartinLiversage: Os arquivos de símbolo são acessíveis de forma que, em uma exceção, junto com a mensagem de exceção, o arquivo de origem e o número da linha possam ser exibidos na saída do depurador. Portanto, a pergunta é: qual é a razão para não incluir mais algumas informações sobre o que exatamente retornou null- observe que o OP não afirma que quer saber que, para compilações de lançamento, também as compilações de depuração podem ser suficientes.
OR Mapper
3
Hmm, e não devemos esquecer que não é nem mesmo o IL que realmente executa, mas sim o código nativo construído a partir dele em tempo de execução.
Oskar Berggren
2
@MartinLiversage: Ninguém nesta pergunta afirma que queremos um suporte perfeito para versões de lançamento. De qualquer forma, não vejo problema em correlacionar o opcode IL que usa uma referência de objeto (que pode acabar sendo null) com a linha e coluna do arquivo de origem que retornou essa referência de objeto.
OR Mapper
9

Como você deseja que a mensagem de erro seja exibida no seguinte caso?

AnyObject.GetANullObject().ToString();

private object GetANullObject()
{
  return null;
}

Não há nomes de variáveis ​​para relatar aqui!

romar
fonte
2
Suspeito que o OP está procurando a expressão no código-fonte que retorna nulo, não o objeto. Eu adicionei um comentário respectivo à pergunta e espero que ele ou ela esclareça as coisas. Se minha suspeita estiver correta, o OP esperaria algo como Object reference obtained from AnyObject.GetANullObject() not set to an instance of an object.a mensagem de erro.
OR Mapper
1
@ORMapper eu concordo. Eu teria apenas colocado minha "resposta" em um comentário ao OP, se tivesse pontos de reputação suficientes para adicionar um comentário!
romar
1
"uma referência nula tentada chamar o método ToString () da Classe XYZ" seria mais útil do que o que obtemos agora.
Michael Levy
A coisa mais útil seria um rastreamento de pilha mostrando, em cada nível de chamada, exatamente qual linha, em qual arquivo, resultou no erro. Oh, espere ... é o que ele faz agora!
Jim Balter
1

Bem, cabe aos engenheiros da Microsoft responder. Mas obviamente você pode usar um depurador e adicionar watch para descobrir qual deles tem um problema.

No entanto, a exceção é o NullReferenceExceptionque significa que a referência não existe . Você não pode obter o objeto que não foi criado.

but why .NET don't tell us which object is null? Porque ele não sabe qual objeto é nulo. O objeto simplesmente não existe!

O mesmo é o caso quando digo, C # é compilado para código .NET IL. O código .NET IL não conhece os nomes ou expressões. Ele só conhece referências e sua localização. Aqui também, você não pode obter o que não existe. A expressão ou o nome da variável não existe.

Filosofia: Você não pode fazer uma omelete se não tiver um ovo.

Aniket Inge
fonte
3
isso não é uma resposta tanto :)
bas
Como você consegue a referência se ela não existe? @Bas
Aniket Inge
4
"Bem, cabe aos engenheiros da Microsoft responder.". Então deixe-os lançar uma luz sobre isso em vez de afirmar o óbvio
bas
@bas podemos decidir o que é lógico, no mínimo. Logicamente, o objeto não existe. Como você vai pegá-lo com uma exceção e imprimir o nome do objeto? Simplesmente não existe. Nem mesmo na pilha ..
Aniket Inge
então a resposta é que é virtualmente impossível apontar qual objeto tem uma referência nula? Essa também é uma resposta. Não estou afirmando que sei, apenas gosto da pergunta :). +1 por todo o esforço: p
bas
1

Não tenho certeza, mas pode ser porque .Net não sabe se é uma classe predefinida ou definida pelo usuário. Se for predefinido, pode ser nulo (como uma string que ocupa 2 bytes), mas se for definido pelo usuário, temos que criar uma instância dele para que saiba que esse objeto ocupará essa quantidade de memória. Portanto, ele lança um erro em tempo de execução.

Oniel Telies
fonte
-2

Boa pergunta. A caixa de mensagem é quase inútil. Mesmo que esteja enterrado a uma milha de profundidade da definição de referências, alguma classe ou montagem ou arquivo ou outra informação seria melhor do que o que eles fornecem atualmente (leia-se: melhor do que nada).

Sua melhor opção é executá-lo no depurador com informações de depuração, e seu IDE quebrará na linha problemática (demonstrando claramente que informações úteis estão de fato disponíveis).

Rick O'Shea
fonte