Si has aprendido el lenguaje C# hace ya unos años, seguro que las limitaciones que han tenido tradicionalmente los switch
te han exasperado más de una vez, ya que básicamente te dejaban hacer una comparación entre una variable y un valor, y poco más.
Si es así, no te preocupes: en este artículo vas a descubrir todo un nuevo mundo de posibilidades que tienes en la actualidad para la toma de decisiones gracias a la coincidencia de modelos en C#.
La coincidencia de modelos o pattern matching es un patrón que nos permite ejecutar un determinado código cuando el dato que recibimos coincida con un modelo determinado. Este patrón de decisión apareció con la versión 7 de C#, pero será en la versión 8 (en septiembre de 2019) cuando se amplíen en serio sus capacidades para crear la potente herramienta que es en la actualidad. Y en C# 9 también se añadieron nuevas posibilidades a esta característica, por lo que descubrirás un nuevo mundo de opciones a la hora de utilizarlos.
Las diferentes opciones que tenemos a la hora de usar coincidencia de modelos son:
Con C# 7:
- Uso en el
if
- Instrucción
switch
Con C# 8:
- Uso de expresiones con
switch
(ojo: no es el switch
tradicional que conoces de toda la vida en C#. Enseguida lo verás...)
- Aplicado en propiedades
- En tuplas
- Empleo posicional
Y, de igual forma, con la versión 9 del lenguaje se incrementaron en 2 patrones más que están basados también en el empleo de la palabra reservada switch
.
- Patrones simples
- Patrones relacionales
- Patrones lógicos
En este primer artículo vamos a ver la coincidencia de modelos básica, tanto en condicionales como en switch
.
Coincidencia de modelos en el if
Cuando trabajas con interfaces o con diferentes tipos de datos puedes usar un switch
para decidir que se ejecute un determinado código u otro en función de que el tipo de dato coincida con una determinada interfaz o un determinado tipo de dato. Es algo muy común.
De la manera tradicional lo conseguimos comprobando en primer lugar el tipo para, posteriormente, realizar un cast. Sería algo así:
private static void MostrarPorConsola(object objeto)
{
if (objeto is Profesor)
{
Profesor profesor = objeto as Profesor;
Console.WriteLine("Profesor: " + profesor.Nombre + " " + profesor.Apellidos);
}
else if (objeto is Alumno)
{
Alumno alumno = objeto as Alumno;
Console.WriteLine("Alumno: " + alumno.Nombre + " " + alumno.Apellidos);
}
else
Console.WriteLine("Función ejecutada");
}
En este caso, lo que se hace es comprobar si el objeto es una instancia de la clase Profesor
o de la clase Alumno
. Si es una de las dos, se ejecuta el código correspondiente. Si no es ninguna de las dos, se ejecuta el código que se pasa como parámetro.
Con la coincidencia de modelos lo que se busca es simplificar el código y reducir la posibilidad de error.
Con este patrón, lo que vamos a definir es en el propio if
la comprobación de tipo exactamente igual que en el ejemplo anterior, pero además incluiremos una variable en la que realizará el volcado en el caso de coincidir el modelo, así:
private static void MostrarPorConsola(object objeto)
{
if (objeto is Profesor profesor)
Console.WriteLine("Profesor: " + profesor.Nombre + " " + profesor.Apellidos);
else if (objeto is Alumno alumno)
Console.WriteLine("Alumno: " + alumno.Nombre + " " + alumno.Apellidos);
else
Console.WriteLine("Función ejecutada");
}
Si observamos el nuevo código, se puede apreciar que es más breve al eliminar la necesidad de realizar el cast nosotros mismos. Además se elimina el riesgo de asignaciones no deseadas, puesto que si no coincide el modelo, no se puede llegar a producir.
Coincidencia de modelos en el switch
La aparición de este patrón en la instrucción switch
altera el comportamiento de forma que, lo que incluiremos en las cláusulas case
, no será un valor fijo o constante como se hace tradicionalmente, sino que podremos incluir también el tipo de dato con el que se tiene que corresponder la variable que le pasamos al switch
.
Esto proporciona una gran potencia adicional. Para ilustrarlo, vamos a modificar el ejemplo anterior de modo que tratemos de forma diferenciada profesores, alumnos y conserjes a través de sus correspondientes clases:
private static void MostrarPorConsola(object objeto)
{
switch (objeto)
{
case Profesor profesor:
Console.WriteLine("Profesor: " + profesor.Nombre + " " + profesor.Apellidos);
break;
case Alumno alumno:
Console.WriteLine("Alumno: " + alumno.Nombre + " " + alumno.Apellidos);
break;
case Conserje conserje:
Console.WriteLine("Conserje: " + conserje.Nombre + " " + conserje.Apellidos);
break;
default:
Console.WriteLine("Función ejecutada");
break;
}
Lo que ocurre en este ejemplo es que, al pasar un dato al switch
, comprueba los diferentes case
en el orden que los hemos puesto. En el momento en que el tipo de objeto
se corresponda con el especificado en el case
, de igual forma que se haría con el operador is
, lo va a asignar a la variable que se encuentra en la derecha del tipo coincidente. Para finalizar ejecutará el código que se encuentra en dicho case
.
Esto proporciona una manera más clara y concisa de realizar este tipo de comprobaciones.
Filtrado en el switch: Cláusula when
Partiendo del código anterior, podremos dar un paso más añadiendo un filtrado al tipo especificado en el case
mediante el uso de la cláusula when
.
De este modo podremos ejecutar un código diferenciado en función de los datos contenidos dentro del objeto en cuestión.
Hay que tener presente, que la cláusula when
contendrá un predicado, es decir, una expresión que devolverá un valor booleano que determinará si se cumple o no la condición.
Repetiremos el código anterior, pero ahora los alumnos y profesores cuyo nombre empiece por "A" los procesaremos de forma diferenciada:
private static void MostrarPorConsola(object objeto)
{
switch (objeto)
{
case Profesor profesor when profesor.Nombre.StartsWith("A"):
Console.WriteLine("Profesor que empieza por A: " + profesor.Nombre + " " + profesor.Apellidos);
break;
case Alumno alumno when alumno.Nombre.StartsWith("A"):
Console.WriteLine("Alumno que empieza por A: " + alumno.Nombre + " " + alumno.Apellidos);
break;
case Profesor profesor:
Console.WriteLine("Profesor: " + profesor.Nombre + " " + profesor.Apellidos);
break;
case Alumno alumno:
Console.WriteLine("Alumno: " + alumno.Nombre + " " + alumno.Apellidos);
break;
case Conserje conserje:
Console.WriteLine("Conserje: " + conserje.Nombre + " " + conserje.Apellidos);
break;
default:
Console.WriteLine("Función ejecutada");
break;
}
MUCHO CUIDADO: es muy importante ordenar correctamente los case
pues si hubiésemos colocado antes las opciones sin when
, nunca llegaría a entrar en las opciones con when
puesto que se cumple la condición antes.
Aunque ya hemos mejorado algo respecto a lo que había antes de C# 7, con esto solo hemos arañado la superficie. En la próxima entrega verás cómo puedes sacarle partido a todas las modalidades adicionales y avanzadas de coincidencia de patrones para crear código con switch
realmente potente pero además fácil de leer y mantener. Hasta pronto...