> Manuales > Manual sobre la plataforma .Net

Qué son os tipos Nullables de .NET y cómo trabajar con ellos para poder convivir con variables que puedan tener valores null.

Indiscutiblemente, los nulos son una de las peores pesadillas del desarrollador, y causa de una gran cantidad de errores en tiempo de ejecución de aplicaciones. La falta de herramientas para tratarlos apropiadamente hace a veces compleja la convivencia con la oveja negra del rebaño de los valores posibles a aplicar a una variable. En este artículo del Manual de .NET vamos a tratar de arrojar un poco de luz y contarte las soluciones que la tecnología nos ofrece para convivir con los valores null.

Y es que con poco que hayáis desarrollado, seguro que alguna vez habéis encontrado la difícil tarea de asignar un valor nulo a una variable de tipo valor (int, float, char...) donde no encaja más que usando artimañas difíciles de realizar, trazar y documentar. Por ejemplo, dada una clase Persona con un propiedad de tipo entero llamada Edad, ¿qué ocurre si cargamos un objeto de dicha clase desde una base de datos si en ésta el campo no era obligatorio?

A priori, fácil: al leer de la base de datos comprobamos si es nulo, y en ese caso le asignamos a la propiedad de la clase el valor -1. Buen parche, sin duda.

Sin embargo, optar de forma general por esta idea presenta varios inconvenientes. En primer lugar, para ser fieles a la realidad, si quisiéramos almacenar de nuevo este objeto en la base de datos, habría que realizar el cambio inverso, es decir, comprobar si la edad es -1 y en ese caso guardar en el campo un nulo.

En segundo lugar, fijaos que estamos llevando a la clase artificios que no tienen sentido en el dominio del problema a resolver, en la entidad a la que representa. Mirándolo un poco desde lejos, ¿qué sentido tiene una edad negativa en una entidad Persona? Ninguno.

En tercer lugar, existe un problema de coherencia en las consultas. Si tengo en memoria una colección de personas (realizada, por ejemplo usando tipos genéricos) y quiero conocer las que tienen edad definida, debería comprobar por cada elemento si su propiedad Edad vale -1; sin embargo, al realizar la misma consulta en la base de datos debería preguntar por el valor NULL sobre el campo correspondiente.

Cierto es que podríamos llevar también a la base de datos el concepto "-1 significa nulo" en el campo Edad, pero... ¿no estaríamos salpicando a la estructura de datos con una particularidad (y limitación) del lenguaje de programación utilizado? Otra idea bizarra podría ser introducir la edad en un string y problema solucionado: las cadenas, al ser un tipo referencia pueden contener nulos sin problemas, sin embargo hay que reconocer que las ordenaciones saldrían regular ;-)

Por último, ¿y si en vez de la edad, donde claramente no pueden existir negativos estuviéramos hablando de la clase CuentaCorriente y su propiedad SaldoActual? Aquí sí que se ve claramente que utilizar negativos no es un recurso válido.

Soluciones, aparte de las comentadas, hay para todos los gustos. Se podría, por ejemplo, añadir una propiedad booleana paralela que indicara si el campo Edad es nulo (un trabajazo extra, sobre todo sin son varias las propiedades que pueden tener estos valores), o encapsular la edad dentro de una clase que incorporara la lógica de tratamiento de este nulo.

En cualquier caso, las soluciones posibles son trabajosas, a veces complejas, y sobre todo, demasiado artificiales para tratarse de algo tan cotidiano como es un simple campo nulo.

Conscientes de ello, los diseñadores de C# tuvieron en cuenta ya en su versión 2.0 una interesante característica: los nullables types, o tipos anulables, un mecanismo que permite introducir el nulo en nuestras vidas de forma no traumática.

La siguiente línea generaba un error en compilación, que decía, y no le faltaba razón, que "no se puede convertir null en 'int' porque es un tipo de valor":

int s = null;

Desde C# 2.0, es posible hacer lo siguiente:

int? s;
s = null;
s = 1;

Obsérvese la interrogación junto al tipo, que es el indicativo de que la variable s es de tipo entero, pero que admite también un valor nulo.

Por dentro, esto funciona de la siguiente forma: int? es un alias del tipo genérico System.Nullable. De hecho, podríamos usar indistintamente cualquiera de las dos formas de expresarlo. Internamente se crea una estructura con dos propiedades de sólo lectura: HasValue, que retorna si la variable en cuestión tiene valor, y Value, que contiene el valor en sí.

Se entiende que una variable con HasValue igual a false contiene el valor nulo, y si intentamos acceder al mismo a través de Value, se lanzará una excepción.

Sin embargo, la principal ventaja que tienen es que se utilizan igual que si fuera un tipo valor tradicional. Los tipos anulables se comportan prácticamente como ellos y ofrecen los mismos operadores, aunque hay que tener en cuenta sus particularidades, como se aprecia en el siguiente código:

int? a = 1;
int? b = 2;
int? intNulo = null;
bool? si = true;
bool? no = false;
bool? niSiNiNo = null;
Console.WriteLine(a + b); // 3
Console.WriteLine(a + intNulo); // Nada, es nulo
Console.WriteLine(a * intNulo); // Nada, es nulo
Console.WriteLine(si & no); // false
Console.WriteLine(si & no); // true
Console.WriteLine(si & niSiNiNo); // Nada, es nulo
Console.WriteLine(no & niSiNiNo); // false
Console.WriteLine(si | niSiNiNo); // true
Console.WriteLine(no | niSiNiNo); // Nada, es nulo

Podemos crear tipos anulables de cualquier tipo valor del framework: int?, float?, double?, char?, etc.

Y de hecho, como tipos valor que son, también podemos hacerlo con enumeraciones. El siguiente ejemplo sería incorrecto:

Estado estado1 = Estado.Correcto;
Estado estado2 = null;

La segunda línea provoca el error en compilación "No se puede convertir null en 'ConsoleApplication1.Program.Estado' porque es un tipo de valor". Lógico, es igual que si intentáramos asignar el maldito valor nulo a un integer.

Esto puede causar ligeras molestias para la gestión de valores "indeterminados", puesto que estaríamos obligados, por ejemplo, a utilizar otro miembro de la enumeración para representar este valor, como en el siguiente código en Visual Basic .Net:

Public Enum Estado
      Indeterminado = 0
      Correcto = 1
      Incorrecto = 2
End Enum

Sin embargo, hay ocasiones en la que resulta más interesante poder disponer del valor nulo como una opción para las variables del tipo de nuestra enumeración. Para ello, podemos utilizar la misma sintaxis que hemos visto anteriormente:

' VB.NET
Dim estado1 As Nullable(Of Estado)
Dim estado2 As Nullable(Of Estado)
estado1 = Estado.Correcto
estado2 = Nothing
// C#
Estado? estado1 = Estado.Correcto;
Estado? estado2 = null;

Nota: Este artículo está basado en diversas publicaciones realizadas anteriormente en el blog del autor, José María Aguilar, Variable Not Found

José María Aguilar

Consultor y desarrollador independiente, Most Valuable Professional (MVP) en ASP...

Manual