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?

Solução

Qual é a causa?

O fundo do poço

Você está tentando utilizar algo que é nulo' (ouNada' 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:

  1. Variáveis de objeto que são **uninicializadas* e portanto pontem para nada.** Neste caso, se você acessar propriedades ou métodos de tais objetos, ele causa uma `NullReferenceException'.
  2. O desenvolvedor está utilizando 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 atribuir null a eles para indicar que não há nenhum valor armazenado nele, por exemplo int? a = null; onde o ponto de interrogação indica que é permitido armazenar null na variável a. Você pode verificar isso com if (a.HasValue) {...} ou com if (a===nulo) {...}. Variáveis nulas, como a este exemplo, permitem acessar o valor via a.Value explicitamente, ou tão normal quanto via a.
    Nota que acessando via a.Value lança uma InvalidOperationException em vez de uma NullReferenceException se a for null - você deve fazer a verificação antes, ou seja, se você tiver outra variável on-nullable int b; então você deve fazer atribuições como if (a.HasValue) { b = a.Value; } ou menor if (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êncianula'. O caso mais simples:

string foo = null;
foo.ToUpper();

Isto irá lançar uma NullReferenceException' na segunda linha porque você não pode chamar o método da instância deToUpper()numa referênciastring' 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

ref1.ref2.ref3.member

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:

var r1 = ref1;
var r2 = r1.ref2;
var r3 = r2.ref3;
r3.member

Especificamente, em HttpContext.Current.User.Identity.Name', a propriedadeHttpContext.Current' pode ser nula, ou a propriedade User' pode ser nula, ou a propriedadeIdentidade' pode ser nula.

Indireto

public class Person {
    public int Age { get; set; }
}
public class Book {
    public Person Author { get; set; }
}
public class Example {
    public void Foo() {
        Book b1 = new Book();
        int authorAge = b1.Author.Age; // You never initialized the Author property.
                                       // there is no Person to get an Age from.
    }
}

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:

Book b1 = new Book { Author = { Age = 45 } };

Isso se traduz para

Book b1 = new Book();
b1.Author.Age = 45;

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

public class Person {
    public ICollection Books { get; set; }
}
public class Book {
    public string Title { get; set; }
}

Os iniciadores de coleções aninhadas comportam-se da mesma maneira:

Person p1 = new Person {
    Books = {
        new Book { Title = "Title1" },
        new Book { Title = "Title2" },
    }
};

Isso se traduz para

Person p1 = new Person();
p1.Books.Add(new Book { Title = "Title1" });
p1.Books.Add(new Book { Title = "Title2" });

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çõesp1.Books.Add(...)`.

Array

int[] numbers = null;
int n = numbers[0]; // numbers is null. There is no array to index.

Elementos de Raios

Person[] people = new Person[5];
people[0].Age = 20 // people[0] is null. The array was allocated but not
                   // initialized. There is no Person to set the Age for.

Jagged Arrays

long[][] array = new long[1][];
array[0][0] = 3; // is null because only the first dimension is yet initialized.
                 // Use array[0] = new long[2]; first.

Colecção/Lista/Dicionário

Dictionary agesForNames = null;
int age = agesForNames["Bob"]; // agesForNames is null.
                               // There is no Dictionary to perform the lookup.

Variável de Intervalo (Indirecto/Diferido)

public class Person {
    public string Name { get; set; }
}
var people = new List();
people.Add(null);
var names = from p in people select p.Name;
string firstName = names.First(); // Exception is thrown here, but actually occurs
                                  // on the line above.  "p" is null because the
                                  // first element we added to the list is null.

Eventos

public class Demo
{
    public event EventHandler StateChanged;

    protected virtual void OnStateChanged(EventArgs e)
    {        
        StateChanged(this, e); // Exception is thrown here 
                               // if no event handlers have been attached
                               // to StateChanged event
    }
}

Bad Naming Conventions:

Se você nomear os campos de forma diferente dos locais, você pode ter percebido que nunca inicializou o campo.

public class Form1 {
    private Customer customer;

    private void Form1_Load(object sender, EventArgs e) {
        Customer customer = new Customer();
        customer.Name = "John";
    }

    private void Button_Click(object sender, EventArgs e) {
        MessageBox.Show(customer.Name);
    }
}

Isto pode ser resolvido seguindo a convenção para prefixar campos com um sublinhado:

private Customer _customer;

Ciclo de vida da página da ASP.NET:

public partial class Issues_Edit : System.Web.UI.Page
{
    protected TestIssue myIssue;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            // Only called on first load, not when button clicked
            myIssue = new TestIssue(); 
        }
    }

    protected void SaveButton_Click(object sender, EventArgs e)
    {
        myIssue.Entry = "NullReferenceException here!";
    }
}

Valores da Sessão ###ASP.NET

// if the "FirstName" session value has not yet been set,
// then this line will throw a NullReferenceException
string firstName = Session["FirstName"].ToString();

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 o Model 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:

// Controller
public class Restaurant:Controller
{
    public ActionResult Search()
    {
         return View();  // Forgot the provide a Model here.
    }
}

// Razor view 
@foreach (var restaurantSearch in Model.RestaurantSearch)  // Throws.
{
}

<p>@Model.somePropertyName</p> 

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. UmaNullReferenceException' 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 da label1. Se a comboBox1_SelectionChanged' tentar referenciar alabel1', ela ainda não terá sido criada.

private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    label1.Content = comboBox1.SelectedIndex.ToString(); // NullReference here!!
}

Alterar a ordem das declarações no XAML (ou seja, listando label1 antes de comboBox1, ignorando questões de filosofia de design, resolveria pelo menos a NullReferenceException aqui.

Elenco com as

var myThing = someObject as Thing;

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() e Single() 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.

 List list = null;    
 foreach(var v in list) { } // exception

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:

 foreach (var node in myData.MyXml.DocumentNode.SelectNodes("//Data"))

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:

void PrintName(Person p) {
    if (p != null) {
        Console.WriteLine(p.Name);
    }
}

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:

string GetCategory(Book b) {
    if (b == null)
        return "Unknown";
    return b.Category;
}

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:

string GetCategory(string bookTitle) {
    var book = library.FindBook(bookTitle);  // This may return null
    if (book == null)
        throw new BookNotFoundException(bookTitle);  // Your custom exception
    return book.Category;
}

Utilizar Debug.Assert se um valor nunca deve ser nulo, 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 usarDebug.Assert()` para quebrar o mais rápido possível quando ele ocorrer:

string GetTitle(int knownBookID) {
    // You know this should never return null.
    var book = library.GetBook(knownBookID);  

    // Exception will occur on the next line instead of at the end of this method.
    Debug.Assert(book != null, "Library didn't return a book for known book ID.");

    // Some other code

    return book.Title; // Will never throw NullReferenceException in Debug mode.
}

Embora essa verificação não acabará na sua compilação do release, fazendo com que ela jogue a NullReferenceException novamente quando book == 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ão nulos.

DateTime? appointment = null;
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the default value provided (DateTime.Now), because appointment is null.

appointment = new DateTime(2022, 10, 20);
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the appointment date, not the default

Use o operador de coalescência nula: ?? [C#] ou If() [VB].

A abreviação para fornecer um valor padrão quando um null é encontrado:

IService CreateService(ILogger log, Int32? frobPowerLevel)
{
    var serviceImpl = new MyService(log ?? NullLog.Instance);

    // Note that the above "GetValueOrDefault()" can also be rewritten to use
    // the coalesce operator:
    serviceImpl.FrobPowerLevel = frobPowerLevel ?? 5;
}

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:

var title = person.Title.ToUpper();

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:

var title = person.Title == null ? null : person.Title.ToUpper();

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:

var title = person.Title?.ToUpper();

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:

// regular null check
int titleLength = 0;
if (title != null)
    titleLength = title.Length; // If title is null, this would throw NullReferenceException

// combining the `?` and the `??` operator
int titleLength = title?.Length ?? 0;

Da mesma forma, para arrays você pode utilizar ?[i] da seguinte forma:

int[] myIntArray=null;
var i=5;
int? elem = myIntArray?[i];
if (!elem.HasValue) Console.WriteLine("No value");

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:

  • habilitar: O contexto de anotação anulável é habilitado. O contexto de aviso de nulidade está habilitado. Variáveis de um tipo de referência, string por exemplo, são não-nuláveis. Todos os avisos de anulabilidade são ativados.
  • desabilitado: O contexto de anotação anulável está desativado. O contexto de aviso de nulidade está desativado. Variáveis de um tipo de referência são alheias, assim como as versões anteriores de C#. Todos os avisos de nulidade estão desabilitados.
  • Somente em segurança: O contexto de anotação anulável está habilitado. O contexto de aviso de nulidade é somente seguro. Variáveis de um tipo de referência não são nulas. Todas as advertências de nulidade de segurança estão habilitadas.
  • advertências: O contexto de anotação anulável está desactivado. O contexto de advertência de nulidade está habilitado. As variáveis de um tipo de referência não são obrigatórias. Todos os avisos de nulidade estão ativados.
  • Avisos de segurança: O contexto de anotação anulável está desactivado. O contexto de advertência de nulidade é salvo. As variáveis de um tipo de referência são alheias. Todas as advertências de nulidade de segurança estão habilitadas. Um tipo de referência nula é anotado utilizando a mesma sintaxe dos tipos de valores nulos: um ? é 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:

public IEnumerable GetFrobs(FrobFactory f, int count)
{
    for (int i = 0; i < count; ++i)
      yield return f.MakeFrob();
}
...
FrobFactory factory = whatever;
IEnumerable frobs = GetFrobs();
...
foreach(Frob frob in frobs) { ... }

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:

// DON'T DO THIS
public IEnumerable GetFrobs(FrobFactory f, int count)
{
    if (f == null) 
      throw new ArgumentNullException("f", "factory must not be null");
    for (int i = 0; i < count; ++i)
      yield return f.MakeFrob();
}

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 é:

// DO THIS
public IEnumerable GetFrobs(FrobFactory f, int count)
{
    // No yields in a public method that throws!
    if (f == null) 
      throw new ArgumentNullException("f", "factory must not be null");
    return GetFrobsForReal(f, count);
}
private IEnumerable GetFrobsForReal(FrobFactory f, int count)
{
    // Yields in a private method
    Debug.Assert(f != null);
    for (int i = 0; i < count; ++i)
      yield return f.MakeFrob();
}

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ãoGetFrobsForReal' 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:

  • desreferenciação de um ponto produz a mesma excepção que desreferenciação de um referenciação nulo
  • desreferenciando um ponteiro não-nulo inválido can produzir essa exceção em algumas circunstâncias Para entender porque isso acontece, ajuda a entender como o .NET produz exceções de desreferenciamento nulo em primeiro lugar. (Estes detalhes se aplicam a .NET rodando no Windows; outros sistemas operacionais usam mecanismos similares). A memória é virtualizada no Windows; cada processo recebe um espaço de memória virtual de muitas "páginas" de memória que são rastreadas pelo sistema operacional. Cada página de memória tem sinalizadores definidos nela que determinam como ela pode ser usada: lida de, escrita, executada, e assim por diante. A página mais baixa é marcada como "produzir um erro se alguma vez for utilizada de alguma forma".
    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.
Comentários (20)

Isso significa que a variável em questão não é apontada para nada. Eu poderia gerar isto desta forma:

SqlConnection connection = null;
connection.Open();

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:

  1. Sempre inicialize seus objetos antes de tentar fazer qualquer coisa com eles.
  2. Se você não tem certeza se o objeto é nulo, verifique com 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.

Comentários (2)

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.

if (myvar != null)
{
    // Go ahead and use myvar
    myvar.property = ...
}
else
{
    // Whoops! myvar is null and cannot be used without first
    // assigning it to an instance reference
    // Attempting to use myvar here will result in NullReferenceException
}
Comentários (0)