1. El problema de NullReferenceException
Prácticamente cualquier programador principiante (y no solo principiante) en C# se ha topado alguna vez con este mensaje aterrador:
System.NullReferenceException: Referencia de objeto no establecida como instancia de un objeto.
Esto es un clásico:
string hello = null;
Console.WriteLine(hello.Length); // ¡BAM! NullReferenceException
La idea es simple: intentas acceder a un objeto que no existe. O sea, la variable apunta a null — un hueco vacío en vez de un objeto.
¿Por qué es fácil cometer este error?
Porque la mayoría de los tipos de referencia en C# históricamente podían tener el valor null. Muchas veces se nos olvida comprobar si la variable realmente apunta a un objeto, y por eso pillamos el famoso NullReferenceException.
Si un programador recibiera un euro por cada NullReferenceException, ya habría escrito su propio sistema operativo.
¿Por qué es un problema?
Antes aprendimos a hacer que valores como int? o double? pudieran ser null — para casos donde necesitamos indicar explícitamente "no hay valor" (por ejemplo, un campo en la base de datos que puede estar vacío).
Pero los tipos de referencia (por ejemplo, string, cualquier clase, arrays, etc.) siempre podían ser null. Así ha sido desde el nacimiento de C#. Es cómodo, pero peligroso — porque el lenguaje no nos obligaba a pensar: "¿Puede ser que esta referencia esté vacía?"
2. Evolución: Nullable Reference Types (NRT)
Hace 5 años apareció una novedad que cambió por completo la forma de luchar contra el NullReferenceException — Nullable Reference Types (NRT).
string s = "hola"; // referencia not-nullable
string? maybe = null; // referencia nullable
La idea principal:
- Distinguir claramente las variables-referencia que seguro no pueden ser null, de las que sí pueden estar vacías.
- Ayudar al programador a ver los posibles problemas con null ya en tiempo de compilación, no en tiempo de ejecución (cuando ya es tarde).
En las nuevas versiones de C# cambió la forma de declarar variables de referencia: por defecto no puedes asignar null, a menos que lo permitas explícitamente.
Así de fácil — solo un símbolo ?, ¡y el significado de la variable cambia por completo!
3. Activando Nullable Reference Types
Por defecto esta sintaxis estricta está desactivada en la mayoría de los proyectos, para no romper el código antiguo. Pero los templates modernos de proyectos en Visual Studio, Rider y .NET CLI ya crean proyectos con NRT activado — o al menos te lo recomiendan mucho.
Para saber seguro si tienes NRT activado en tu proyecto, busca en el archivo .csproj la línea:
<Nullable>enable</Nullable>
Si no está esa línea, puedes añadirla a mano — es seguro.
¿Cómo afecta esto al código?
- Si NRT está desactivado (comportamiento antiguo): string s = null; — ningún error, a nadie le importa.
- Si está activado: el compilador se quejará si intentas meter un null donde no debería haber null.
4. Ejemplos con NRT
Ejemplo sencillo
#nullable enable // Esta línea activa la comprobación NRT para este archivo
string notNullable = "Hola";
string notNullable2 = null; // ¡ERROR de compilación!
string? nullableString = null; // Todo bien, hemos permitido null explícitamente
En la primera línea declaramos una string que siempre debe apuntar a un objeto real.
En la tercera — declaramos una string que puede ser null.
Comprobando null
void PrintLength(string? s)
{
// El compilador se quejará: "¿Y si s == null?"
Console.WriteLine(s.Length);
// Así sí - todo bien
if (s != null)
{
Console.WriteLine(s.Length);
}
}
¡Ahora el compilador te ayuda a no olvidarte de las comprobaciones!
Advertencias del compilador
Si ignoramos la advertencia y aún así intentamos acceder a una variable nullable sin comprobar — recibimos una nueva (¡y muy útil!) advertencia del compilador. No es un error (el programa sigue compilando), pero la "luz amarilla" te dice: "¿Seguro, colega?"
Comparando modos de trabajo:
| Tipo C# | ¿Puede ser null? | Modo Legacy (antes de NRT) | Modo NRT (#nullable enable) |
|---|---|---|---|
| int | No | No | No |
| int? | Sí | Sí | Sí |
| string | Sí | Sí (siempre se puede) | No (por defecto no se puede) |
| string? | Sí | No | Sí |
5. Consejos: cómo y por qué usar NRT
- Menos bugs: Menos caídas inesperadas por culpa de null, más felicidad para devs y usuarios.
- Código más claro: Se ve al toque qué puede estar vacío y qué siempre debe estar relleno.
- Ayuda del compilador: Hay que admitirlo — ¡realmente intenta ayudarnos! Las advertencias NRT son oro para detectar errores potenciales.
¿Dónde es imprescindible?
- En proyectos grandes donde mucha gente toca el mismo código.
- En APIs y librerías públicas — para dejar claro a otros qué pueden y qué no pueden hacer.
- Donde la fiabilidad es clave (por ejemplo, apps bancarias, sistemas médicos, etc.)
6. Errores típicos y trampas
- "Olvidé poner el ?"
Asignas null a una string normal (string s = null;) — y el compilador se queja, porque ahora por defecto una string normal no debe ser null. - "Me pasé con el ?"
Haces todas las variables string? solo para que el compilador no proteste. Pero la idea es marcar con cuidado dónde realmente se permite un valor vacío. - "No entendí la advertencia"
Ignoras la advertencia y luego pillas un NullReferenceException justo donde esperabas que el compilador te cuidara.
GO TO FULL VERSION