44

En numerosas ocasiones se recibe una NullReferenceException, siendo una de las excepciones que más preguntas suscita. ¿Qué significa NullReferenceException y cómo se puede solucionar?

Para el NullReferenceException en VB.NET, véase NullReferenceException: ¿Qué es y cómo puedo solucionarla?

Pikoh
  • 17,305
  • 9
  • 38
  • 54

2 Answers2

62

¿Cuál es la causa?

Esencialmente

Estás intentando utilizar algún objeto que es null (o Nothing en VB.NET). Esto significa que, o lo has inicializado a null, o en ningun momento lo inicializaste.

null es un valor como los demás,y se pasa de la misma manera. Si tienes null en el método "A", puede ser que el método "B" haya pasado un null al método "A".

El resto de esta respuesta va a entrar en más detalle y mostrar errores que muchos programadores cometen habitualmente y que pueden llevar a obtener una NullReferenceException.

Más específicamente

Cuando el motor de tiempo de ejecución (runtime) lanza una NullReferenceException siempre significa lo mismo: estás intentando usar una referencia. Esta referencia no se ha inicializado (o fue inicializada en algún momento , pero ya no está inicializada).

Esto significa que la referencia es null, y no se puede acceder a los miembros de una referencia si ésta es null. Este es el caso mas simple:

string variable = null;
variable.ToUpper();

Este código lanzará una NullReferenceException en la segunda línea, ya que no se puede llamar al método de instancia ToUpper() en una referencia de string que apunta a null.

Depuración

¿Cómo encuentro el origen de una excepción NullReferenceException? Además de estudiando la excepción en si, que será lanzada en el lugar exacto en el que ocurre, se deben aplicar las reglas generales de depuración en Visual Studio: poner puntos de ruptura (breakpoints) estratégicamente e inspeccionar las variables, bien poniendo el cursor del ratón encima de sus nombres, abriendo una ventanta de Inspección (Rápida) o bien usando los paneles de depuración existentes como Variables locales y Automático.

Si quieres encontrar dónde la referencia está o no asignada, pulsa con el botón derecho en su nombre y elige "Encontrar todas las referencias". A continuación puedes poner un punto de ruptura en todas las localizaciones encontradas y ejecutar de nuevo el programa en depuración. Cada vez que el depurador pare la ejecución en ese punto, necesitarás determinar si esperabas que la referencia en ese punto no fuera nula, inspeccionando la variable y verificando que apunta a una instancia cuando debería.

Siguiendo el flujo del programa de esta manera, podrás encontrar la localización donde la instancia no debería ser nula, y por qué no ha sido inicializada como debería.

Ejemplos

Algunos escenarios comunes donde podemos encontrar esta excepción:

Genéricos

ref1.ref2.ref3.miembro

Si ref1 o ref2 o ref3 son null, entonces obtendrás una NullReferenceException. Si quieres resolver este problema, entonces deberás encontrar cuál de ellas es null reescribiendo la expresión a su equivalente más sencillo:

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

Por ejemplo, en HttpContext.Current.User.Identity.Name, el HttpContext.Current podría ser null, o la propiedad User podría ser null, o la propiedad Identity podría ser null.

Instancias de clase

Cuando se crea cualquier variable de tipo de referencia (de clase), por defecto esta es inicializada a null.

public class Libro {
    public string Titulo { get; set; }
}
public class Ejemplo {
    public void Prueba() {
        Libro b1;
        string titulo = b1.Titulo;  // Nunca has inicializado la variable b1
                                    // asi que no hay ningun libro del que obtener el título
    }
}

Las variables de Clase deben o ser inicializadas o asignadas con una instancia de la misma clase ya inicializada. La inicialización se consigue utilizando la palabra clave new.

Libro b1 = new Libro();

Indirecta

public class Persona {
    public int Edad { get; set; }
}
public class Libro {
    public Persona Autor { get; set; }
}
public class Ejemplo {
    public void Prueba() {
        Libro b1 = new Libro();
        int edadAutor = b1.Autor.Edad; // La propiedad Autor nunca ha sido inicializada
                                       // asi que no existe ninguna Persona de la que obtener su edad.
    }
}

Si quieres evitar la referencia nula del hijo (Persona), podrías inicializarla en el constructor del padre (Libro).

Esto mismo aplica a los inicializadores de objetos anidados:

Libro b1 = new Libro { Autor = { Edad = 45 } };

Aunque en este ejemplo se usa la palabra clave new, ésta solo crea una nueva instancia de Libro, pero no una nueva instancia de Persona, así que la propiedad Autor sigue siendo null.

Arrays

int[] numeros = null;
int n = numeros[0]; // numeros es null. No pueden existir los indices.

Elementos de un Array

Persona[] gente = new Persona[5];
gente[0].Edad = 20 // gente[0] es null. El array ha sido asignado en memoria pero no inicializado
                   // No existe ninguna persona para la que asignar la edad.

Arrays Irregulares (Jagged Arrays)

long[][] array = new long[1][];
array[0][0] = 3; // es null porque sólo la primera dimensión ha sido inicializada.
                 // Se debe usar array[0] = new long[2]; primero.

Colecciones/Listas/Diccionarios

Dictionary<string, int> edadesPorNombres = null;
int age = edadesPorNombres["Bob"]; // edadesPorNombres is null.
                                   // No existe ningún Diccionario donde realizar la búsqueda.

Variables de rango (Indirectas/Diferidas)

public class Persona {
    public string Nombre { get; set; }
}
var gente = new List<Persona>();
gente.Add(null);
var nombres = from p in gente select p.Nombre;
string primerNombre = nombres.First(); // La Excepción se lanza aquí, pero en realidad ha ocurrido
                                       // en la línea anterior.  "p" es null porque el primer
                                       // elemento que añadimos a la lista es null

Eventos

public class Demo
{
    public event EventHandler EstadoCambiado;

    protected virtual void OnEstadoCambiado(EventArgs e)
    {        
        EstadoCambiado(this, e); // La excepción es lanzada aquí
                                 // si no existe ningún manejador de eventos acoplado 
                                 // al evento EstadoCambiado
    }
}

Nombres que no siguen las convenciones:

Si hubieras nombrado los campos de manera diferente a las variables locales, podrías haber notado que nunca inicializaste el campo.

public class Form1 {
    private Cliente cliente;

    private void Form1_Load(object sender, EventArgs e) {
        Cliente cliente = new Cliente();
        cliente.Nombre = "Juan";
    }

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

Esto se puede solucionar siguiendo la convención de que a los campos se les añade una barra baja (_) antes del nombre:

private Cliente _cliente;

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

public partial class EdicionProblemas : System.Web.UI.Page
{
    protected ProblemaTest miProblema;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            // Solo se llama en la primera carga, no cuando se pulsa el botón
            miProblema= new ProblemaTest(); 
        }
    }

    protected void BotonGuardar_Click(object sender, EventArgs e)
    {
        myProblema.Entrada = "¡NullReferenceException aqui!";
    }
}

Valores de Sesión de ASP.NET

// si el valor de sesión "Nombre" no ha sido añadido todavía,
// entonces esta línea lanzará una NullReferenceException
string nombre = Session["Nombre"].ToString();

ASP.NET MVC View Models vacíos

Si la excepción ocurre cuando se referencia una propiedad de @Modelo en una vista de ASP.NET MVC, necesitas entender que el Modelo se inicializa en tu método action, cuando devuelves(return) una vista. Cuando devuelves un modelo vacío (o una propiedad del modelo) desde tu controlador, la excepción es lanzada cuando la vista accede a él:

// Controlador
public class Restaurante:Controller
{
    public ActionResult Buscar()
    {
         return View();  // Olvidaste proporcionar un modelo aqui.
    }
}

// Razor view 
@foreach (var restauranteBuscar in Model.BuscarRestaurante)  // Se lanza la excepción.
{
}

<p>@Model.algunaPropiedad</p> <!-- tambien se lanza la excepción -->

Orden de creación de controles y eventos en WPF

Los controles WPF son creados durante la llamada a InitializeComponent en el orden en el que aparecen en el árbol visual. Una excepción NullReferenceException puede producirse en el caso de que controles creados antes con manejadores de eventos, etc. , que se disparan durante InitializeComponent, referencien controles creados después.

Por ejemplo:

<Grid>
    <!-- Combobox se declara primero -->
    <ComboBox Name="comboBox1" 
              Margin="10"
              SelectedIndex="0" 
              SelectionChanged="comboBox1_SelectionChanged">
        <ComboBoxItem Content="Elemento 1" />
        <ComboBoxItem Content="Elemento 2" />
        <ComboBoxItem Content="Elemento 3" />
    </ComboBox>

    <!-- Label declarado después -->
    <Label Name="label1" 
           Content="Etiqueta"
           Margin="10" />
</Grid>

Aqui comboBox1 es creado antes que label1. Si el evento comboBox1_SelectionChanged intenta referenciar a label1, éste todavía no ha sido creado.

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

Cambiando el orden de las declaraciones en el XAML (por ejemplo, poniendo label1 antes que comboBox1, ignorando los problemas de la filosofía de diseño), al menos resolvería la NullReferenceException en este caso.

Cast con as

var miCosa = unObjeto as Cosa;

Esto no lanzaría una InvalidCastException, sino que devolvería null cuando falle el cast (y cuando unObjeto sea null). Así que hay que tener cuidado con ello.

LINQ FirstOrDefault() y SingleOrDefault()

Las versiones sencillas First() y Single() lanzan una excepción cuando no hay nada. Las versiones "OrDefault" devuelven null en ese caso. Debes ser consciente de ello.

foreach

foreach lanza una excepción cuando intentas iterar una colección nula. Normalmente la causa de esto es un resultado nulo no esperado de un método que devuelve una colección.

 List<int> lista = null;    
 foreach(var v in lista) { } // excepción

Un ejemplo mas realista - seleccionar nodos de un documento XML. Se lanzará la excepción si los nodos no se encuentran pero una depuración inicial muestra que todas las propiedades son válidas:

 foreach (var nodo in myData.MyXml.DocumentNode.SelectNodes("//Datos"))

Maneras de evitarlo

Comprobar explícitamente null e ignorar los valores nulos.

Si prevés que la referencia alguna vez puede ser nula, puedes preguntar si es null antes de acceder a los miembros de la instancia:

void ImprimirNombre(Persona p) {
    if (p != null) {
        Console.WriteLine(p.Nombre);
    }
}

Comprobar explícitamente null y devolver un valor por defecto.

La llamada a los métodos que esperas que devuelvan una instancia podrían devolver null, por ejemplo cuando el objeto que se busca no se encuentra. Puedes optar por devolver un valor por defecto en estos casos:

string ObtenerCategoria(Libro b) {
    if (b == null)
        return "Desconocida";
    return b.Categoria;
}

Comprobar explícitamente null y lanzar una excepción personalizada.

También podrías lanzar una excepción personalizada, para poder capturarla en el código que llama al método:

string ObtenerCategoria(string titulo) {
    var libro = biblioteca.BuscarLibro(titulo);  // Esto podría devolver null
    if (libro == null)
        throw new LibroNoEncontradoException(titulo);  // Tu excepción personalizada
    return libro.Categoria;
}

Usa Debug.Assert si un valor nunca debería ser null, para capturar el problema antes de que la excepción sea lanzada.

Cuando en la etapa de desarrollo sepas que un método podría devolver null aunque esto no debería ocurrir nunca, puedes usar Debug.Assert() para parar el flujo de programa lo antes posible cuando pase:

string ObtenerTitulo(int libroExistenteID) {
    // Sabes que esto nunca debería devolver null.
    var libro = biblioteca.ObtenerLibro(libroExistenteID);  

    // La excepción se lanzará en la linea siguiente en lugar de la final del método.
    Debug.Assert(libro != null, "Biblioteca no devolvió ningun libro para el ID de un libro existente.");

    // ...otro código...

    return book.Titulo; // Nunca se lanzará una NullReferenceException en modo de depuración(Debug).
}

Este control no se realizará en tu compilación de lanzamiento (Release Build), causando que la NullReferenceException sea lanzada de nuevo cuando libro == null en tiempo de ejecución.

Usar GetValueOrDefault() en los tipos por valor "Anulables"(nullables) para obtener un valor por defecto cuando sean nulos.

DateTime? cita = null;
Console.WriteLine(cita.GetValueOrDefault(DateTime.Now));
// Mostrará el valor por defecto proporcionado (DateTime.Now), porque cita es null.

cita = new DateTime(2022, 10, 20);
Console.WriteLine(cita.GetValueOrDefault(DateTime.Now));
// Mostrará la fecha de la cita, no el valor por defecto

Usar el operador de comprobación de valores nulos (null coalescing operator): ?? [C#] o If() [VB].

La manera más corta de proporcionar un valor por defecto cuando se encuentra un valor nulo:

IService CrearServicio(ILogger log, Int32? nivel)
{
    var serviceImpl = new MiServicio(log ?? NullLog.Instance);

    // Fíjate que el "GetValueOrDefault()" de arriba puede ser reescrito para usar el operador ??
    serviceImpl.nivel = nivel ?? 5;
}

Usar el operador de condición nula: ?. (disponible en C# 6 y VB.NET 14):

A este operador también se le llama de navegación segura o Elvis (por su forma). Si la expresión de la parte izquierda del operador es null, la parte derecha del mismo no será evaluada, y en su lugar se devolverá null. En este caso:

var titulo = persona.Titulo.ToUpper();

Si la persona no tiene un título, este código lanzará una excepción porque se está tratando de llamar a ToUpper en una propiedad con un valor nulo.

En C# 5 e inferiores, esto se puede solucionar así:

var titulo = persona.Titulo == null ? null : persona.Titulo.ToUpper();

Ahora la variable titulo será null en lugar de lanzar una excepción. C# 6 introduce una sintáxis mas corta que hace lo mismo:

var titulo = persona.Titulo?.ToUpper();

El resultado de esto es que a la variable titulo se le asignará null, y la llamada a ToUpper nunca se realizará si persona.Titulo es null.

Por supuesto, todavía tienes que comprobar que title no sea null o usar el operador de condición nula junto al operador de comprobación de valores nulos (??) para proporcionar un valor por defecto:

// comprobación normal de null
int longitudTitulo = 0;
if (titulo != null)
    longitudTitulo = titulo.Length; // Si titulo es null, esto lanzará una NullReferenceException

// combinando los operadores `?` y `??`
int longitudTitulo = titulo?.Length ?? 0;

Esta respuesta es una traducción de la magnifica respuesta que se encuentra en la versión en inglés de Stack Overflow

Pikoh
  • 17,305
  • 9
  • 38
  • 54
0

En mi caso tenia un List inicializado en null

List<Object> lista = null;

Cambie la inicializacion por el tipo de objeto y se resolvió

List<Object> lista = new List<Object>();