1. Introducción
Imagina esta situación. Guardas datos sobre gatos en tu código. Y quieres, además de la edad del gato, guardar estados como "no hay datos", "edad desconocida". Podrías guardar 0, pero cero es una edad válida.
int age = 0; // Edad del gato. Pero ¿qué significa 0? ¿Es un gatito o "desconocido"?
El problema es que int solo acepta valores enteros, y "no hay datos" no está previsto entre los enteros. Si la edad fuera una cadena, podrías asignar null. Pero con números eso no funciona:
int age = null; // ¡Error! No puedes asignar null a una variable de tipo int
Esto pasa porque los tipos de valor (structs como int, double, DateTime, bool) siempre almacenan algo. No tienen un estado "vacío" (a diferencia de los tipos por referencia, donde null simplemente significa que no hay objeto).
Ejemplo de la vida real:
Intentando saltarse esta limitación, los programadores a veces inventan valores "especiales": por ejemplo, -1 o int.MaxValue para "sin valor". Pero eso es feo, incómodo y peligroso: es fácil confundir un valor real con un placeholder.
2. Tipo Nullable: un tipo que puede ser "null"
La idea
¿Y si permitimos que los números normales (y en general todos los tipos de valor) puedan guardar no solo un valor, sino también null?
En C# esto se hace con una clase especial envoltorio llamada Nullable<T>. Esta clase puede contener o bien un valor de tipo T, o nada (o sea, null).
Sintaxis
int? age = null; // La variable age puede ser un número o null
El compilador de C#, cuando ve este código, en realidad lo interpreta así:
Nullable<int> age = new Nullable<int>(); // La variable age no tiene valor
O, si quieres indicar el valor explícitamente:
Nullable<int> age = new Nullable<int>(42); // age contiene el número 42
Así, el signo de interrogación convierte cualquier tipo de valor en su versión nullable:
- int? (lo mismo que Nullable<int>)
- double?
- DateTime?
- y cualquier otra estructura
Wrappers Nullable para los tipos estructurados más populares
| Tipo normal | Tipo Nullable | Ejemplo de declaración |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
También puedes escribirlo de forma más larga, usando la clase envoltorio:
Nullable<int> pages = null;
Pero escribir int? es más corto, y por tanto, más guay.
3. Cómo usar un tipo nullable
Asignación y comprobación
Asignar null funciona igual que con los tipos por referencia:
Declaración y asignación
int? temperature = null;
temperature = 25;
Comprobación de null
¿Cómo saber si nuestro tipo nullable tiene un valor real?
if (temperature != null)
{
Console.WriteLine($"Temperatura: {temperature}");
}
else
{
Console.WriteLine("No hay datos de temperatura");
}
Propiedades de los tipos Nullable: .HasValue y .Value
Los tipos nullable tienen dos propiedades importantes:
- .HasValue — devuelve true si la variable tiene algo (no es null).
- .Value — el valor en sí, si existe (si no, lanza una excepción).
int? temperature = null;
if (temperature.HasValue) //¡no habrá excepción!
{
Console.WriteLine($"Temperatura: {temperature.Value}°C");
}
else
{
Console.WriteLine("Temperatura desconocida");
}
Este código funciona: temperature no es igual a null, contiene null por dentro. Y siempre puedes llamar a la propiedad HasValue de un tipo Nullable. Pero intentar obtener .Value cuando no hay valor es mala idea: te comes una excepción InvalidOperationException. ¡Comprueba si hay valor antes!
Sintaxis corta para mostrar valores
En muchos casos basta con usar la variable de tipo nullable — y C# lo entiende solo:
int? hours = null;
Console.WriteLine(hours); // no muestra nada (simplemente vacío)
hours = 10;
Console.WriteLine(hours); // muestra 10
4. Operaciones y tipos nullable: aritmética, comparaciones, conversiones
Operaciones: ¿qué pasa?
Si uno de los operandos es nullable, el resultado también es nullable.
Y si uno de los operandos es null, el resultado también es null.
int? a = 5;
int? b = null;
int? sum = a + b; // sum == null
int? c = 10;
int? d = 15;
int? total = c + d; // total == 25
Operaciones de comparación
Puedes comparar tipos nullable con números normales:
int? score = null;
if (score > 0) Console.WriteLine("¡Buen resultado!");
else Console.WriteLine("Sin resultado"); // Se ejecuta esta rama
Si score es null, la condición score > 0 será false.
Conversión explícita e implícita
- De tipo normal a nullable: automático.
- De nullable a normal: solo si no es null (si no, excepción).
int? x = 3;
int y = x.Value; // Ok, si x no es null
// Implícito
int? z = y;
5. Nullable y métodos: parámetros y valores de retorno
Devolver valores nullable
Si un método no siempre puede devolver un resultado, usa tipos nullable como valor de retorno:
// El método busca un usuario por login y devuelve su edad si lo encuentra, si no, null
int? FindUserAge(string login)
{
// ... aquí va la lógica de búsqueda
return null; // si no se encuentra
}
Parámetros nullable
Puedes aceptar valores nullable como parámetros, para dejar claro: “puede haber valor, o no”.
void PrintTemperature(int? temp)
{
if (temp == null)
Console.WriteLine("Temperatura desconocida");
else
Console.WriteLine($"Temperatura: {temp} grados");
}
GO TO FULL VERSION