O que é uma NullReferenceException, e como corrigi-la?
Eu tenho algum código e quando ele executa, ele lança um "NullReferenceException", dizendo:
Referência do objeto não definida para uma instância de um objeto.
O que significa isto, e o que posso fazer para corrigir este erro?
1876
3
Qual é a causa?
O fundo do poço
Você está tentando utilizar algo que é
nulo' (ou
Nada' em VB.NET). Isto significa que ou você o define como 'nulo', ou você nunca o define para nada. Como qualquer outra coisa,nulo
é passado de mão em mão. Se é 'nulo' no método "A", pode ser que o método "B" passou um método 'nulo' a método "A". "Nulo" pode ter significados diferentes:null
intencionalmente para indicar que não há nenhum valor significativo disponível. Note que C# tem o conceito de datatypes nulos para variáveis (como tabelas de banco de dados podem ter campos nulos) - você pode atribuirnull
a eles para indicar que não há nenhum valor armazenado nele, por exemploint? a = null;
onde o ponto de interrogação indica que é permitido armazenar null na variávela
. Você pode verificar isso comif (a.HasValue) {...}
ou comif (a===nulo) {...}
. Variáveis nulas, comoa
este exemplo, permitem acessar o valor viaa.Value
explicitamente, ou tão normal quanto viaa
.Nota que acessando via
a.Value
lança umaInvalidOperationException
em vez de umaNullReferenceException
sea
fornull
- você deve fazer a verificação antes, ou seja, se você tiver outra variável on-nullableint b;
então você deve fazer atribuições comoif (a.HasValue) { b = a.Value; }
ou menorif (a != null) { b = a; }
. O resto deste artigo entra em mais detalhes e mostra erros que muitos programadores frequentemente cometem e que podem levar a uma `NullReferenceException'.Mais especificamente
O tempo de execução lançando uma
NullReferenceException
sempre significa a mesma coisa: você está tentando utilizar uma referência, e a referência não está inicializada (ou foi once inicializada, mas não está mais inicializada). Isto significa que a referência énula', e você não pode acessar membros (tais como métodos) através de uma referência
nula'. O caso mais simples:Isto irá lançar uma
NullReferenceException' na segunda linha porque você não pode chamar o método da instância de
ToUpper()numa referência
string' apontando para `null'.Debugging
Como encontrar a origem de uma "NullReferenceException"? Além de olhar para a própria exceção, que será lançada exatamente no local onde ela ocorre, as regras gerais de depuração no Visual Studio se aplicam: coloque pontos de interrupção estratégicos e inspecione suas variáveis, seja passando o mouse sobre seus nomes, abrindo uma janela (Quick)Watch ou utilizando os vários painéis de depuração como Locals e Autos. Se você quiser descobrir onde a referência está ou não definida, clique com o botão direito do mouse sobre seu nome e selecione "Find All References" (Encontrar todas as referências). Você pode então colocar um ponto de parada em cada local encontrado e executar seu programa com o depurador anexado. Cada vez que o depurador quebra em tal ponto de quebra, você precisa determinar se espera que a referência não seja nula, inspecionar a variável e verificar se ela aponta para uma instância quando você espera que ela aponte. Ao seguir o fluxo do programa desta forma, você pode encontrar o local onde a instância não deve ser nula, e por que ela não está devidamente definida.
Exemplos
Alguns cenários comuns onde a exceção pode ser jogada:
Genérico
Se a ref1, ref2 ou ref3 for nula, então você terá uma `NullReferenceException'. Se você quiser resolver o problema, então descubra qual deles é nulo, reescrevendo a expressão para o seu equivalente mais simples:
Especificamente, em
HttpContext.Current.User.Identity.Name', a propriedade
HttpContext.Current' pode ser nula, ou a propriedadeUser' pode ser nula, ou a propriedade
Identidade' pode ser nula.Indireto
Se você quiser evitar a referência nula da criança (Pessoa), você poderia inicializá-la no construtor do objeto dos pais (Livro).
Inicializadores de Objetos Nested
O mesmo se aplica aos inicializadores de objetos aninhados:
Isso se traduz para
Enquanto a palavra-chave "nova" é utilizada, ela apenas cria uma nova instância de "Livro", mas não uma nova instância de "Pessoa", então o "Autor" a propriedade ainda é "nula".
Nested Collection Initializers
Os iniciadores de coleções aninhadas comportam-se da mesma maneira:
Isso se traduz para
A "nova Pessoa" apenas cria uma instância de "Pessoa", mas a coleção de "Livros" ainda é "nula". A sintaxe do inicializador da coleção não cria uma coleção para
p1.Books', ele só se traduz para as declarações
p1.Books.Add(...)`.Array
Elementos de Raios
Jagged Arrays
Colecção/Lista/Dicionário
Variável de Intervalo (Indirecto/Diferido)
Eventos
Bad Naming Conventions:
Se você nomear os campos de forma diferente dos locais, você pode ter percebido que nunca inicializou o campo.
Isto pode ser resolvido seguindo a convenção para prefixar campos com um sublinhado:
Ciclo de vida da página da ASP.NET:
Valores da Sessão ###ASP.NET
Modelos de visão vazia do MVC da ASP.NET
Se a exceção ocorrer ao referenciar uma propriedade de
@Model
em uma view ASP.NET MVC, você precisa entender que oModel
fica definido no seu método de ação, quando vocêretorna
uma view. Quando você retorna um modelo vazio (ou propriedade do modelo) do seu controller, a exceção ocorre quando as views acessam a ele:WPF Ordem de Criação de Controle e Eventos
Os controles WPF são criados durante a chamada para
InitializarComponente' na ordem em que aparecem na árvore visual. Uma
NullReferenceException' será criada no caso de controles criados antecipadamente com manipuladores de eventos, etc. A exceção de "InitializeComponent", que faz referência aos controles criados tardiamente. Por exemplo :Aqui a
comboBox1
é criada antes dalabel1
. Se acomboBox1_SelectionChanged' tentar referenciar a
label1', ela ainda não terá sido criada.Alterar a ordem das declarações no XAML (ou seja, listando
label1
antes decomboBox1
, ignorando questões de filosofia de design, resolveria pelo menos aNullReferenceException
aqui.Elenco com
as
Isso não joga uma InvalidCastException, mas retorna um 'nulo' quando o elenco falha (e quando algum objeto é nulo por si só). Então esteja atento a isso.
LINQ FirstOrDefault() e SingleOrDefault()
As versões simples
First()
eSingle()
lançam exceções quando não há nada. As versões "OrDefault" retornam nulas nesse caso. Portanto, esteja ciente disso.para cada
quando se tenta iterar a recolha nula. Normalmente causado por 'nulos' inesperados resultam de métodos que retornam coleções.
Exemplo mais realista - selecione os nós do documento XML. Irá lançar se os nós não forem encontrados, mas a depuração inicial mostra que todas as propriedades são válidas:
Formas de Evitar
Explicitamente verificar por
nulo
e ignorar valores nulos.Se você espera que a referência às vezes seja nula, você pode verificar se ela é `nula' antes de acessar os membros da instância:
Explicitamente verifique por
nulo
e forneça um valor padrão.Os métodos que você espera retornar uma instância podem retornar
nulo
, por exemplo, quando o objeto procurado não pode ser encontrado. Você pode escolher retornar um valor padrão quando este for o caso:Explicitamente verifique por
nulo
de chamadas de método e lance uma exceção personalizada.Você também pode lançar uma exceção personalizada, apenas para pegá-la no código de chamada:
Utilizar
Debug.Assert
se um valor nunca deve sernulo
, para pegar o problema antes que a exceção ocorra.Quando você sabe durante o desenvolvimento que um método talvez possa, mas nunca deve retornar
nulo', você pode usar
Debug.Assert()` para quebrar o mais rápido possível quando ele ocorrer:Embora essa verificação não acabará na sua compilação do release, fazendo com que ela jogue a
NullReferenceException
novamente quandobook == null
em tempo de execução no modo release.Use
GetValueOrDefault()
para tipos de valores nulos para fornecer um valor padrão quando eles sãonulos
.Use o operador de coalescência nula:
??
[C#] ouIf()
[VB].A abreviação para fornecer um valor padrão quando um
null
é encontrado:Use o operador de condição nula:
?.
ou?[x]
para arrays (disponível em C# 6 e VB.NET 14):Isso também é às vezes chamado de operador de navegação segura ou Elvis (depois de sua forma). Se a expressão no lado esquerdo do operador for nula, então o lado direito não será avaliado, e nulo é retornado em seu lugar. Isso significa casos como este:
Se a pessoa não tiver um título, isto irá lançar uma excepção porque está a tentar chamar
ToUpper
a um imóvel com um valor nulo. Em C# 5 e abaixo, isto pode ser guardado com:Agora a variável título será nula em vez de lançar uma exceção. C# 6 introduz uma sintaxe mais curta para isto:
Isto fará com que a variável título seja "nula", e a chamada para "ToUpper" não será feita se "person.Title" for "nula". É claro, você até tem que verificar
title
para nulo ou utilizar o operador de condição nulo junto com o operador de condição nulo (??
) para fornecer um valor padrão:Da mesma forma, para arrays você pode utilizar
?[i]
da seguinte forma:Isto vai fazer o seguinte: Se o myIntArray for nulo, a expressão retorna nula e você pode verificá-la com segurança. Se ele contém um array, ele fará o mesmo que:
elem = myIntArray[i];
e retorna o elemento ith.Use null context (disponível em C# 8):
Introduzido em C# 8, os tipos de contexto nulo e referência nula realizam análises estáticas em variáveis e fornecem um aviso do compilador se um valor pode ser potencialmente nulo ou ter sido definido como nulo. Os tipos de referência nulos permitem que os tipos sejam explicitamente autorizados a serem nulos. O contexto de anotação nula e o contexto de aviso nulo podem ser definidos para um projeto usando o elemento Nullable no seu arquivo csproj. Este elemento configura como o compilador interpreta a nulidade dos tipos e que avisos são gerados. As configurações válidas são:
?
é anexado ao tipo da variável.Técnicas especiais para depuração e correção de derefs nulos em iteradores
O C# suporta "blocos iteradores" (chamados "geradores" em alguns outros idiomas populares). Exceções de desreferência nula podem ser particularmente difíceis de depurar em blocos iterator por causa da execução diferida:
Se "o que quer que seja" resultar em "nulo" então "MakeFrob" irá lançar. Agora, você pode pensar que a coisa certa a fazer é isto:
Porque é que isto está errado? Porque o bloco do iterador na verdade não executou até o
para cada
! A chamada para 'GetFrobs' simplesmente retorna um objeto que quando iterada irá rodar o bloco do iterador. Ao escrever uma verificação nula como esta você evita a dereferência nula, mas você move a exceção do argumento nulo para o ponto da iteração, não para o ponto da chamada, e isso é muito confuso para debug. A correção correta é:Ou seja, fazer um método de ajuda privada que tenha a lógica de bloco de iterador, e um método de superfície pública que faça a verificação nula e devolva o iterador. Agora quando
GetFrobs' é chamado, a verificação nula acontece imediatamente, e então
GetFrobsForReal' executa quando a seqüência é iterada. Se você examinar a fonte de referência do LINQ para Objetos, você verá que esta técnica é utilizada em todo o processo. É um pouco mais difícil de escrever, mas torna a depuração de erros de nulidade muito mais fácil. Optimize seu código para a conveniência do chamador, não para a conveniência do autor.Uma nota sobre as dereferências nulas em código inseguro
C# tem um modo "inseguro" que é, como o nome indica, extremamente perigoso porque os mecanismos normais de segurança que fornecem segurança de memória e tipo de segurança não são aplicados. **Você não deve estar escrevendo código inseguro a menos que tenha uma compreensão completa e profunda de como a memória funciona***. No modo inseguro, você deve estar ciente de dois fatos importantes:
Tanto um ponteiro nulo como uma referência nula em C# são representados internamente como o número zero, e assim qualquer tentativa de desreferenciá-lo em seu correspondente armazenamento de memória faz com que o sistema operacional produza um erro. O tempo de execução .NET então detecta esse erro e o transforma na exceção de desreferência nula. É por isso que o desreferenciamento tanto de um ponteiro nulo quanto de uma referência nula produz a mesma exceção. E quanto ao segundo ponto? A dereferenciação qualquer ponteiro inválido que cai na página mais baixa da memória virtual causa o mesmo erro no sistema operacional e, portanto, a mesma exceção. Porque é que isto faz sentido? Bem, suponha que temos uma estrutura contendo duas polegadas, e um ponteiro não administrado igual a nulo. Se tentarmos desreferenciar a segunda int na estrutura, o CLR não tentará acessar o armazenamento no local zero; ele acessará o armazenamento no local quatro. Mas logicamente isso é uma dereferência nula porque estamos chegando a esse endereço via o nulo. Se você está trabalhando com código inseguro e obtém uma exceção de desreferência nula, basta estar ciente de que o ponteiro ofensivo não precisa ser nulo. Pode ser qualquer localização na página mais baixa, e esta excepção será produzida.
Isso significa que a variável em questão não é apontada para nada. Eu poderia gerar isto desta forma:
Isso vai lançar o erro porque, embora eu tenha declarado a variável "conexão", ela não é apontada para nada. Quando eu tento chamar o membro de "Open", não há referência para ele resolver, e ele irá lançar o erro.
Para evitar este erro:
objeto == nulo
.A ferramenta JetBrains Resharper identificará todos os lugares no seu código que tenham a possibilidade de um erro de referência nulo, permitindo que você coloque uma verificação nula. Este erro é a fonte número um de bugs, IMHO.
Isso significa que o seu código usou uma variável de referência de objeto que foi definida como nula (ou seja, não se referia a uma instância de objeto real).
Para evitar o erro, os objetos que poderiam ser nulos devem ser testados para nulos antes de serem usados.