La instrucción switch clásica permitía comparar un valor con constantes y ejecutar bloques de código. El switch expression, introducido en versiones recientes del lenguaje, va más allá y devuelve un valor según el patrón que encaje. Combinado con patrones de propiedad, tupla y lista, permite expresar lógica que antes requería cadenas largas de if-else if.
Sintaxis compacta y exhaustividad
Un switch expression se escribe con el valor a analizar, la palabra clave switch y una lista de ramas patrón => expresión. El resultado completo es una expresión, por lo que puede asignarse a una variable o devolverse directamente.
public string CategoriaEdad(int anios) => anios switch
{
< 0 => throw new ArgumentOutOfRangeException(nameof(anios)),
< 13 => "Infante",
< 18 => "Adolescente",
< 65 => "Adulto",
_ => "Senior"
};
El compilador realiza un análisis de exhaustividad y avisa si detecta valores no cubiertos. El caso final _ actúa como catch-all para cualquier valor no tratado por los patrones anteriores. La escritura es más breve que un if-else anidado y, sobre todo, obliga a devolver un valor por cada rama.
Si alguna rama puede no devolver un valor y en su lugar lanza una excepción, el compilador no rompe la exhaustividad. Lanzar es una forma válida de completar la rama.
Patrones relacionales y lógicos
Los patrones relacionales comparan el valor con constantes usando operadores como <, <=, > o >=. Se combinan con patrones lógicos and, or y not para expresar rangos complejos sin variables auxiliares.
public string ClasificarTemperatura(double grados) => grados switch
{
< -10 => "Muy frio",
>= -10 and < 0 => "Frio",
0 => "Punto de congelacion",
> 0 and < 20 => "Templado",
>= 20 and <= 30 => "Calido",
> 30 => "Caluroso",
double.NaN => "Medida invalida",
_ => "Fuera de rango"
};
El patrón double.NaN ilustra cómo encajar constantes de tipo flotante dentro del mismo switch. El uso de and y or suprime la necesidad de comparar grados en cada rama.
Los patrones negados con not expresan ausencia de una condición, útil para filtrar casos extremos.
public string DescribirLongitud(string? texto) => texto switch
{
null => "Sin valor",
not null and { Length: 0 } => "Cadena vacia",
{ Length: < 10 } => "Corto",
_ => "Largo"
};
Patrones de propiedad
Un patrón de propiedad examina miembros del objeto sin necesidad de asignar variables previas. Se escribe con llaves y la lista de propiedades que deben cumplir una condición.
public record Pedido(int Id, string Estado, decimal Total, Cliente? Cliente);
public record Cliente(string Nombre, bool EsVip);
public string ProcesarPedido(Pedido pedido) => pedido switch
{
{ Estado: "Cancelado" } => "Ignorado",
{ Total: > 1000, Cliente.EsVip: true } => "Prioridad alta",
{ Total: > 1000 } => "Prioridad media",
{ Cliente: null } => "Revisar cliente",
_ => "Normal"
};
El patrón accede a propiedades anidadas como Cliente.EsVip. Si el camino completo existe y cumple la condición, el patrón encaja. Si Cliente es nulo, el patrón { Cliente.EsVip: true } sencillamente no coincide y se evalúa la siguiente rama sin excepción.
Los patrones de propiedad son alternativas declarativas a los
ifque comprueban una propiedad tras otra. El código queda alineado por patrones, no por indentación.
Guardas con when
Cuando una rama necesita una comprobación que no puede expresarse como patrón, se usa la cláusula when. La rama encaja si el patrón coincide y además la condición resulta verdadera.
public string Evaluar(Pedido pedido, DateTime referencia) => pedido switch
{
{ Estado: "Pendiente" } p when p.Total > 5000 => "Requiere aprobacion",
{ Estado: "Pendiente" } p when (referencia - DateTime.UtcNow).TotalDays > 7 => "Urgente",
{ Estado: "Cerrado" } => "Archivar",
_ => "Revisar"
};
La variable p captura el pedido en cada rama y queda accesible dentro del when. Las guardas son perfectas para combinar estado con parámetros externos como la fecha de referencia, el usuario que invoca la operación o configuraciones del sistema.
Patrones de tupla
Se pueden analizar varios valores a la vez envolviéndolos en una tupla literal. Cada elemento se compara con su patrón. Esta variante se aplica cuando la decisión depende de la combinación de dos o más dimensiones.
public string DecidirTransporte(int distanciaKm, bool urgente) =>
(distanciaKm, urgente) switch
{
(< 2, _) => "A pie",
(< 10, false) => "Bicicleta",
(< 10, true) => "Patinete",
(< 500, true) => "Tren de alta velocidad",
(< 500, false) => "Tren convencional",
(_, true) => "Avion",
_ => "Tren nocturno"
};
El uso de _ en cualquier posición de la tupla indica cualquier valor. La lectura es inmediata: cada línea expresa una regla de negocio independiente.
flowchart LR
A[Distancia y urgencia] --> B{Switch tupla}
B -->|distancia 2km| C[A pie]
B -->|10km no urgente| D[Bicicleta]
B -->|10km urgente| E[Patinete]
B -->|500km urgente| F[Tren AVE]
B -->|más| G[Avión o tren nocturno]
Patrones de lista
Los patrones de lista analizan el contenido de un array o una lista comparando posición a posición. Se escriben con corchetes e incluyen elementos literales, comodines con _ o el operador .. para capturar el resto.
public string ClasificarComando(string[] args) => args switch
{
[] => "Sin argumentos",
["help"] => "Mostrar ayuda",
["version"] => "Mostrar version",
["run", var modulo] => $"Ejecutar modulo {modulo}",
["run", var modulo, ..var flags] => $"Ejecutar {modulo} con flags {string.Join(',', flags)}",
[var primero, ..] => $"Comando desconocido: {primero}",
_ => "Entrada invalida"
};
El patrón [] encaja con un array vacío. El patrón ["run", var modulo] exige exactamente dos elementos y captura el segundo. El operador ..var flags recoge cero o más elementos restantes en una variable de tipo T[]. Este mecanismo evita escribir bucles manuales para parsear comandos o analizar rutas.
Los patrones de lista se pueden combinar con los patrones posicionales y de propiedad para casos más elaborados.
public record Coordenada(int X, int Y);
public string AnalizarRuta(Coordenada[] ruta) => ruta switch
{
[] => "Vacia",
[{ X: 0, Y: 0 }] => "Origen",
[var primera, .., var ultima] when primera == ultima => "Circular",
[_, _, _, ..] => "Larga",
_ => "Corta"
};
Recursión y polimorfismo con switch
Los switch expressions encajan muy bien con jerarquías de tipos. Al combinarlos con patrones de tipo se obtiene una forma de despachar que recuerda al pattern matching de lenguajes funcionales.
public abstract record Forma;
public record Circulo(double Radio) : Forma;
public record Rectangulo(double Base, double Altura) : Forma;
public record Triangulo(double Base, double Altura) : Forma;
public double Area(Forma forma) => forma switch
{
Circulo { Radio: > 0 } c => Math.PI * c.Radio * c.Radio,
Rectangulo r => r.Base * r.Altura,
Triangulo t => t.Base * t.Altura / 2,
_ => throw new InvalidOperationException("Forma desconocida.")
};
Si se añade un nuevo tipo de forma, el caso por defecto protege el comportamiento en producción, pero el compilador no avisa automáticamente. Para forzar la exhaustividad, es útil declarar la jerarquía como sealed o como abstract con una lista cerrada, y apoyarse en analizadores estáticos que avisen cuando se introduce un tipo sin rama propia.
Buenas prácticas de lectura
Un switch expression bien escrito se lee como una tabla de decisiones. Cada rama es independiente y no depende del orden, salvo por el efecto natural del primer patrón que encaja. Algunos hábitos que ayudan a mantener la claridad son los siguientes.
Primero, ordenar los patrones del más específico al más general. Los patrones con propiedades concretas y guardas van arriba, los comodines abajo. Esta disciplina evita ramas inalcanzables.
Segundo, romper en varios switch cuando la lógica se vuelve demasiado grande. Si hay más de diez ramas con guardas complejas, probablemente exista una jerarquía de casos que puede extraerse a una estructura de datos intermedia.
Tercero, aprovechar funciones locales cuando el cuerpo de una rama es más largo que una expresión simple. El switch sigue siendo una expresión, pero cada rama puede delegar en un método local para no crecer horizontalmente.
public string Procesar(Pedido pedido) => pedido switch
{
{ Estado: "Pendiente" } p => ProcesarPendiente(p),
{ Estado: "Cancelado" } p => RegistrarCancelacion(p),
{ Estado: "Cerrado" } p => Archivar(p),
_ => throw new InvalidOperationException($"Estado {pedido.Estado} no soportado.")
};
Aplicando estas pautas, los switch expressions se convierten en una herramienta principal para expresar lógica de negocio discreta sin perder legibilidad a medida que el código crece.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, C# es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de C#
Explora más contenido relacionado con C# y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Escribir switch expressions expresivos con patrones combinados, propiedades anidadas, guardas when, tuplas y patrones de lista para simplificar condiciones complejas.