Switch expressions con patrones avanzados

Avanzado
C#
C#
Actualizado: 18/04/2026

La instruccion switch clasica permitia comparar un valor con constantes y ejecutar bloques de codigo. El switch expression, introducido en versiones recientes del lenguaje, va mas alla y devuelve un valor segun el patron que encaje. Combinado con patrones de propiedad, tupla y lista, permite expresar logica que antes requeria 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 patron => expresion. El resultado completo es una expresion, 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 analisis de exhaustividad y avisa si detecta valores no cubiertos. El caso final _ actua como catch-all para cualquier valor no tratado por los patrones anteriores. La escritura es mas 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 excepcion, el compilador no rompe la exhaustividad. Lanzar es una forma valida de completar la rama.

Patrones relacionales y logicos

Los patrones relacionales comparan el valor con constantes usando operadores como <, <=, > o >=. Se combinan con patrones logicos 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 patron double.NaN ilustra como 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 condicion, util 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 patron 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 condicion.

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 patron accede a propiedades anidadas como Cliente.EsVip. Si el camino completo existe y cumple la condicion, el patron encaja. Si Cliente es nulo, el patron { Cliente.EsVip: true } sencillamente no coincide y se evalua la siguiente rama sin excepcion.

Los patrones de propiedad son alternativas declarativas a los if que comprueban una propiedad tras otra. El codigo queda alineado por patrones, no por indentacion.

Guardas con when

Cuando una rama necesita una comprobacion que no puede expresarse como patron, se usa la clausula when. La rama encaja si el patron coincide y ademas la condicion 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 parametros externos como la fecha de referencia, el usuario que invoca la operacion o configuraciones del sistema.

Patrones de tupla

Se pueden analizar varios valores a la vez envolviendolos en una tupla literal. Cada elemento se compara con su patron. Esta variante se aplica cuando la decision depende de la combinacion de dos o mas 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 posicion de la tupla indica cualquier valor. La lectura es inmediata: cada linea 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 -->|mas| G[Avion o tren nocturno]

Patrones de lista

Los patrones de lista analizan el contenido de un array o una lista comparando posicion a posicion. 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 patron [] encaja con un array vacio. El patron ["run", var modulo] exige exactamente dos elementos y captura el segundo. El operador ..var flags recoge cero o mas 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 mas 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"
};

Recursion y polimorfismo con switch

Los switch expressions encajan muy bien con jerarquias 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 anade un nuevo tipo de forma, el caso por defecto protege el comportamiento en produccion, pero el compilador no avisa automaticamente. Para forzar la exhaustividad, es util declarar la jerarquia como sealed o como abstract con una lista cerrada, y apoyarse en analizadores estaticos que avisen cuando se introduce un tipo sin rama propia.

Buenas practicas 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 patron que encaja. Algunos habitos que ayudan a mantener la claridad son los siguientes.

Primero, ordenar los patrones del mas especifico al mas 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 logica se vuelve demasiado grande. Si hay mas de diez ramas con guardas complejas, probablemente exista una jerarquia de casos que puede extraerse a una estructura de datos intermedia.

Tercero, aprovechar funciones locales cuando el cuerpo de una rama es mas largo que una expresion simple. El switch sigue siendo una expresion, pero cada rama puede delegar en un metodo 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 logica de negocio discreta sin perder legibilidad a medida que el codigo crece.

Alan Sastre - Autor del tutorial

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.