Autor en Google+
Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET, ASP.NET Core, MVC, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

13 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET, ASP.NET Core, MVC, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 15 de octubre de 2019
.NET Core Seguimos descubriendo perlas en C# 8 que nos harán la vida algo más sencilla a los desarrolladores. En este caso, se trata de una pequeña adición al lenguaje que nos permitirá hacer más claras y concisas determinadas expresiones condicionales.

Para ponernos en situación, imaginemos que tenemos una expresión como la siguiente, donde retornamos el texto "Rojo" cuando le suministramos el valor de enumeración Color.Red, y "Desconocido" en otros casos. Algo fácil de solucionar utilizando el operador condicional ?:
enum Color { Purple, Red, Blue, Orange, Black, Pink, Gray, Green, White };
string GetColorName(Color color)
{
    var str = color == Color.Red ? "Rojo" : "Desconocido";
    return str;
}
Imaginemos ahora que la aplicación evoluciona y debemos añadir otro caso a esta condición, como el soporte para el color azul. No pasa nada, podemos seguir el mismo patrón, aunque empezaremos a notar que esto no va a escalar demasiado porque la legibilidad empieza a resentirse:
var str = color == Color.Red ? "Rojo" : color == Color.Blue ? "Azul" : "Desconocido";

Podemos suponer cómo acabará esto cuando el número de casos a contemplar comiencen a dispararse. Seguro que todos tenemos código como el siguiente, donde ya cuesta trabajo ver qué casos han sido implementados y, si son correctos:
var str = color == Color.Red ? "Rojo" : color == Color.Blue ? "Azul" : 
  color == Color.Orange? "Naranja": color == Color.Purple? "Púrpura": 
  color == Color.Black? "Negro": color == Color.Green? "Verde": 
  color == Color.Purple? "Violeta": color == Color.Gray? "Gris": 
  color == Color.Pink? "Rosa": color == Color.White? "Blanco": "Desconocido";
Es cierto que hasta el momento teníamos soluciones más o menos razonables al problema. Por ejemplo, podríamos formatear el código para que quede más legible poniendo cada condición en una línea. Con esto solucionaríamos el problema de la legibilidad, pero no podríamos verificar su corrección, algo difícil de hacer a ojo si se trata de un buen número de opciones.
Fijaos que en el código anterior hemos repetido deliberadamente el caso Color.Purple. Es fácil pasar por alto este error cuando el código es extenso.
Por supuesto, podríamos considerar el uso de opciones más artesanas, como la implementación mediante diccionarios o similares, pero resultarían algo verbosas y no impedirían la duplicación de valores:
var dic = new Dictionary<Color, string>()
{
    [Color.Red] = "Rojo",
    [Color.Blue] = "Azul",
    [Color.Orange] = "Naranja",
    [Color.Purple] = "Púrpura",
    [Color.Black] = "Negro",
    [Color.Green] = "Verde",
    [Color.Gray] = "Gris",
    [Color.Purple] = "Violeta",
    ...
};
if (!dic.TryGetValue(color, out string result))
    result = "Desconocido";
Pero lo más razonable sería refactorizar a un bloque switch. En este caso podríamos asegurar que no cometemos los errores de antes, pero a cambio tendríamos que introducir bastante código, lo cual incidiría negativamente en la legibilidad:
string result;
switch(color)
{
    case Color.Red:
        result = "Rojo";
        break;
    case Color.Blue:
        result = "Azul";
        break;
    case Color.Orange:
        result = "Naranja";
        break;
    case Color.Purple:
        result = "Púrpura";
        break;
    ...
    default:
        result = "Desconocido";
        break;
}
Pues bien, en este punto es donde entran las expresiones switch de C# 8, que nos permiten implementar una mezcla con lo mejor de las opciones anteriores: la inmediatez del operador condicional ? con la potencia del switch:
var str = color switch
{
    Color.Red => "Rojo",
    Color.Blue => "Azul",
    Color.Orange => "Naranja",
    Color.Purple => "Púrpura",
    Color.Black => "Negro",
    Color.Green => "Verde",
    Color.Gray => "Gris",
    Color.Pink=> "Rosa",
    _ => "Desconocido"
};
Observad que sintácticamente recuerda algo a los bloques switch de toda la vida, pero tiene algunas diferencias importantes:
  • La variable objeto de la condición aparece antes de la palabra clave switch.
  • Se usa "=>" para determinar el retorno en cada caso.
  • Cada opción se resuelve en una expresión, no hay bloques de código ni break para distinguirlas.
  • El caso default se implementa también de forma más concisa, con el carácter de descarte "_".
Por último, es interesante indicar que, dado que internamente se comporta como un switch, es posible utilizar toda la potencia que últimamente se ha dado a este tipo de construcciones con pattern matching para implementar escenarios más complejos:
object obj = GetSomething();
Console.WriteLine(obj switch
{
    Color color when color == Color.Red => "Rojo",
    Color color => color.ToString(),
    string str => str,
    int i => i.ToString("#,##0")
});

Publicado en: www.variablenotfound.com.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

2 Comentarios:

LuigiMX dijo...

¿Se puede incluír mas de una línea haciendo algo como esto: Color.Red => { /* líneas */ } ?

¿si no se usa el "default", ¿el compilador verifica que todos los casos se dan? algo como lo que hace el pattern matching de f#.

La verdad es un paso mas hacia una mejor programación funcional en c#, yo espero como agua de mayo la versión 9 de c#, cosas como Records o las Type Classes me parece a mi que harán que el lenguaje evolucione muchisimo.

José María Aguilar dijo...

Hola!

Creo que de momento sólo pueden ir expresiones "inline", y no bloques de código. Tiene sentido, pues si al final se pudieran meter bloques probablemente nos desviaríamos de los objetivos iniciales de la agilidad y facilidad de lectura... pero quién sabe, esto seguirá evolucionando.

Por otra parte, si el compilador detecta que no cubres todas las posibilidades, se generará un warning:

CS8509: The switch expression does not handle all possible inputs (it is not exhaustive).

Saludos!

Artículos relacionados: