Repasando Null-Coalescing Operator y Null-Coalescing Assignment Operator y convirtiendo tipos nullable a tipos no nullable
Quiero pensar que conoces el operador Null-Coalescing ?? y que posiblemente lo utilices en tus aplicaciones.
Igualmente quiero pensar que también conoces el operador Null-Coalescing Assignment Operator ??= introducido en C# 8.0.
Pues bien, vamos a repasar algunos conceptos y aplicaciones que tienen estos operadores pasando por algún otro operador que seguramente también utilices.
Un pequeño ejemplo de inicio introductorio sería el siguiente:
Partimos de una clase EmployeeRegistration que nos permitirá registrar el acceso de nuestros empleados a nuestra empresa.
public class EmployeeRegistration { public int IdEmployee { get; } public DateTime EntryDateTime { get; } public EmployeeRegistration(int idEmployee, DateTime entryDateTime) { IdEmployee = idEmployee; EntryDateTime = entryDateTime; } }
Ahora supongamos que obtenemos la fecha y hora de registro de dos empleados, y queremos asegurarnos de tener uno de ellos válido no nulo (se supone que sabiendo que al menos uno de ellos no lo es).
Sin Null-Coalescing haríamos algo así:
EmployeeRegistration employee1 = null; EmployeeRegistration employee2 = new EmployeeRegistration(2, DateTime.UtcNow); EmployeeRegistration result; if (employee1 != null) result = employee1; else result = employee2;
Con Conditional Operator (?:) sería de la siguiente forma:
EmployeeRegistration employee1 = null; EmployeeRegistration employee2 = new EmployeeRegistration(2, DateTime.UtcNow); var result = (employee1 != null) ? employee1 : employee2;
Y con Null-Coalescing lo haríamos de esta forma:
EmployeeRegistration employee1 = null; EmployeeRegistration employee2 = new EmployeeRegistration(2, DateTime.UtcNow); var result = employee1 ?? employee2;
Dentro de result obtendríamos el valor de employee1 siempre que no sea null, y en su caso contrario employee2.
Ahora bien, Null-Coalescing nos permite hacer el código más compacto.
Por otra parte, Conditional Operator (?:) y Null-Coalescing nos permiten anidar tantas evaluaciones como queramos.
Pero por contra, hacer esto con Conditional Operator (?:) tiene como connotación negativa que el código es poco legible.
Null-Coalescing por su parte nos permite hacer más legible el código.
Esta forma de anidar operandos es desconocida por muchos programadores o en otras ocasiones no es utilizada correctamente.
Utilizando Null-Coalescing en operandos anidados podríamos hacer algo parecido a lo siguiente:
EmployeeRegistration employee1 = null; EmployeeRegistration employee2 = null; EmployeeRegistration employee3 = null; EmployeeRegistration employee4 = new EmployeeRegistration(4, DateTime.UtcNow); var result = employee1 ?? employee2 ?? employee3 ?? employee4;
No obstante, vayamos un poco más allá.
Realmente no tiene mucho sentido realizar esta acción si lo que queremos es obtener un resultado no nullable y no tenemos la certeza absoluta de que así sea.
Es decir, result podría valer null y no hay forma de garantizar lo contrario.
¿Cómo podríamos garantizar que el resultado obtenido no es null?.
Pues dentro del uso de Null-Coalescing hemos visto que podemos anidar varios operandos, así que si hacemos un pequeño esfuerzo y pensamos en una claúsula if o una claúsula switch, la última evaluación en términos generales de programación debería ser el valor por defecto.
Pues aquí podemos obviamente hacer exactamente lo mismo.
Por lo que la última evaluación devolverá un objeto no nullable por defecto que a su vez nos pueda permitir evaluar que tiene un valor no nullable y válido para la lógica de negocio encargada de consumirlo.
Nuestro código anterior quedaría finalmente de la forma:
EmployeeRegistration employee1 = null; EmployeeRegistration employee2 = null; EmployeeRegistration employee3 = null; EmployeeRegistration employee4 = null; var result = employee1 ?? employee2 ?? employee3 ?? employee4 ?? new EmployeeRegistration(-1, DateTime.MinValue);
De esta forma tan sencilla, podemos asegurarnos en Null-Coalescing que el valor que nos va a devolver el operando NO es null.
Pero vayamos más allá aún.
Con C# 8.0, se introdujo al lenguaje Null-Coalescing Assignment Operator.
Nota: Recordemos que C# 7.3 y anteriores están soportados en .NET Core 2.x y C# 8.0 en .NET Core 3.x.
Null-Coalescing Assignment Operator permite asignar a la parte izquierda del operador la parte derecha del mismo, y su uso es igual que Null-Coalescing pero con un signo igual al final, es decir, utilizando el operador ??=.
Un pequeño y rápido ejemplo de cómo se hacía esto antes de existir este operando sería el siguiente:
EmployeeRegistration employee1 = null; EmployeeRegistration employee2 = new EmployeeRegistration(2, DateTime.UtcNow); if (employee1 is null) employee1 = employee2;
Ese mismo ejemplo pero utilizando Null-Coalescing Assignment Operator quedaría de la siguiente forma:
EmployeeRegistration employee1 = null; EmployeeRegistration employee2 = new EmployeeRegistration(2, DateTime.UtcNow); employee1 ??= employee2;
En este ejemplo, employee1 tendría el mismo contenido que employee2.
Y aquí un punto y aparte.
En este punto y si pensamos un poco más, tenemos delante nuestra la posibilidad de utilizar este operando aportándonos un valor extra, así que aquí va un truco que quiero compartir.
Podríamos reutilizar este operando de asignación como hacíamos con Null-Coalesting para asegurarnos que no vamos a trabajar con valores null.
Nuestro código anterior quedaría por lo tanto de la siguiente forma:
EmployeeRegistration employee1 = null; EmployeeRegistration employee2 = null; EmployeeRegistration employee3 = null; EmployeeRegistration employee4 = null; employee1 ??= employee2 ??= employee3 ??= employee4 ??= new EmployeeRegistration(-1, DateTime.MinValue);
De esta forma, podemos inicializar todos los empleados del ejemplo a un valor por defecto válido para la lógica de negocio y que no sea null.
Eso sí, un aviso a los más despistados.
Ten en cuenta que en el ejemplo estamos trabajando con tipos por referencia asignando un objeto sobre otro.
Esto quiere decir que el consejo aquí más que nunca es que esos objetos sean inmutables para evitar cambiar en cualquiera de ellos el valor de alguna propiedad que lo contiene, ya que en este caso, el valor se cambiaría para todos ellos.
Como es obvio, esto no sucedería en tipos por valor como por ejemplo:
int? one = null; int? two = null; int? three = null; int? four = null; one ??= two ??= three ??= four ??= 999; one = 111;
Aquí one valdrá un valor concreto que no afectará al valor del resto de variables.
Como bonus, te aconsejo acceder y leer la entrada que publiqué hace algunos meses sobre la especificación de C# 8.0 y Nullable reference types que encontrarás aquí.
Happy Coding!