Menu

Expresión switch en C#: sintaxis, patrones, descartes y when

La expresión switch de C# 8 convierte un valor en un resultado con brazos de patrones en lugar de case y break. Sintaxis, el descarte _, patrones de propiedad, de tupla y relacionales, and/or/not, guardas when, avisos de exhaustividad y SwitchExpressionException, con equivalentes ejecutables en C# 7.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Una expresión switch asocia un valor de entrada a un resultado. Se añadió en C# 8 y sustituye al patrón habitual de una sentencia switch en la que cada case solo devuelve o asigna algo:

// C# 8 and later
string StatusText(int code) => code switch
{
    200 => "OK",
    301 or 302 => "Redirect",   // 'or' needs C# 9
    404 => "Not Found",
    >= 500 => "Server Error",   // relational patterns need C# 9
    _ => "Unknown"
};

Léela de arriba abajo: el valor que va antes de switch se prueba contra cada brazo en orden, y el primer brazo cuyo patrón coincide aporta el resultado. No hay palabra clave case, ni break, ni default; _ (el patrón de descarte) coincide con cualquier cosa y sirve de alternativa final.

En C# 7, la misma asociación es una sentencia switch en un método pequeño, con return en cada case:

Salida:

200 OK
302 Redirect
404 Not Found
503 Server Error
418 Unknown

La forma de expresión es más corta sobre todo porque cada brazo ocupa una línea y el cuerpo del método desaparece. Además es una expresión, así que puede ir en cualquier lugar donde vaya un valor: un argumento, un hueco de interpolación, un return, el inicializador de un campo.

Reglas de sintaxis

var result = input switch
{
    pattern1 => value1,
    pattern2 when condition => value2,
    _ => fallback
};
  • Primero va la entrada y después switch. Es el orden inverso al de la sentencia.
  • Cada brazo es pattern => expression. El lado derecho debe ser una sola expresión; para más lógica, llama a un método.
  • Los brazos se separan con comas. Se permite una coma final después del último brazo.
  • Todo el conjunto es una expresión, así que la sentencia que la usa termina con ; después de la llave de cierre.
  • Todos los brazos deben producir valores de un tipo común. var x = n switch { 0 => "zero", _ => 0 }; no compila (CS8506, no hay un tipo mejor). Desde C# 9, un tipo de destino declarado lo resuelve: object x = n switch { 0 => "zero", _ => 0 }; compila.
  • Cada brazo debe producir un valor, así que un brazo no puede llamar a un método void como Console.WriteLine, y una expresión switch no puede ir sola como sentencia (CS0201). Para acciones, usa la sentencia switch.
  • Un brazo puede lanzar una excepción en lugar de producir un valor: _ => throw new ArgumentOutOfRangeException(nameof(input)).
  • El compilador rechaza un brazo que nunca puede alcanzarse porque uno anterior ya lo cubre, con el error CS8510, The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. Pon los brazos específicos antes que los generales.

Patrones relacionales y lógicos (C# 9)

C# 9 añadió <, <=, >, >= como patrones, y los combinadores and, or y not. Con ellos las tablas de rangos se leen como una especificación:

// C# 9 and later
string Grade(int score) => score switch
{
    < 0 or > 100 => throw new ArgumentOutOfRangeException(nameof(score)),
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    >= 60 => "D",
    _ => "F"
};

string AgeGroup(int age) => age switch
{
    < 13 => "child",
    >= 13 and <= 19 => "teen",
    _ => "adult"
};

bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

Como los brazos se prueban en orden, >= 80 solo ve puntuaciones por debajo de 90. Los patrones relacionales solo admiten constantes a la derecha; para comparar con una variable, usa una guarda when. El equivalente en C# 7 de una tabla de rangos es una cadena de if:

Salida:

95 -> A
83 -> B
71 -> C
64 -> D
12 -> F

not se usa casi siempre como not null: x is not null, o un brazo not null => x.Name.

Patrones de propiedad

Un patrón de propiedad comprueba los miembros de un objeto: { Property: pattern, ... }. Combinado con una expresión switch, expresa reglas de negocio sin una cadena de condiciones &&:

// C# 8 and later
decimal ShippingCost(Order order) => order switch
{
    { Total: >= 100m } => 0m,                       // relational inside: C# 9
    { Country: "US", Express: true } => 15m,
    { Country: "US" } => 5m,
    { Express: true } => 30m,
    null => throw new ArgumentNullException(nameof(order)),
    _ => 12m
};

Todas las propiedades indicadas deben coincidir para que coincida el brazo. Un patrón de propiedad vacío { } coincide con cualquier valor que no sea null. C# 10 añadió los patrones de propiedad extendidos para miembros anidados: { Customer.Address.Country: "US" } en lugar de { Customer: { Address: { Country: "US" } } }.

Patrones de tupla

Hacer switch sobre una tupla comprueba varios valores a la vez, que es la forma limpia de expresar una tabla de decisión:

// C# 9 and later (the 'or' pattern)
string Winner(string a, string b) => (a, b) switch
{
    ("rock", "scissors") or ("scissors", "paper") or ("paper", "rock") => "player 1",
    var (x, y) when x == y => "draw",
    _ => "player 2"
};

var (x, y) deconstruye la tupla en dos variables que la guarda when puede comparar. En C# 7, el sustituto habitual es combinar los valores en una sola clave, o usar un diccionario de resultados:

Salida:

player 1
player 2
draw

Patrones de tipo y when

Un brazo puede comprobar el tipo en tiempo de ejecución y enlazar una variable, y una cláusula when añade cualquier condición que la sintaxis de patrones no puede expresar:

// C# 8 and later
double Area(Shape shape) => shape switch
{
    Circle c => Math.PI * c.Radius * c.Radius,
    Rectangle r when r.Width == r.Height => r.Width * r.Width,
    Rectangle r => r.Width * r.Height,
    null => throw new ArgumentNullException(nameof(shape)),
    _ => throw new NotSupportedException(shape.GetType().Name)
};

La versión de C# 7 usa el patrón de tipo is en una cadena de if, que enlaza la variable de la misma forma:

Salida:

Circle: 12.57
Rectangle: 13.50

Exhaustividad y SwitchExpressionException

Una expresión switch debe producir un valor para cada entrada. Cuando el compilador ve entradas con las que no coincide ningún brazo, avisa:

warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '0' is not covered.

Es un aviso, no un error, así que el código se compila igualmente. Si en tiempo de ejecución llega un valor sin coincidencia, la expresión lanza System.Runtime.CompilerServices.SwitchExpressionException, cuyo mensaje termina con el valor sin coincidencia (Unmatched value was 3.).

Los enums tienen un caso especial. Aunque cada miembro con nombre tenga un brazo, una variable enum puede contener cualquier entero ((Size)3), así que el compilador da CS8524 por "un valor de enum sin nombre":

// C# 8 and later
enum Size { Small, Medium, Large }

string Code(Size s) => s switch
{
    Size.Small => "S",
    Size.Medium => "M",
    Size.Large => "L",
    // warning CS8524 without the next arm
    _ => throw new ArgumentOutOfRangeException(nameof(s))
};

Añade un brazo _ explícito que lance una excepción útil. Así documentas la suposición, silencias el aviso y obtienes un error más claro que SwitchExpressionException si alguna vez aparece un valor incorrecto.

Un Dictionary como alternativa

Cuando los brazos son pares puros de constante a valor y la tabla puede crecer o venir de la configuración, un Dictionary hace el mismo trabajo en cualquier versión de C# y puede construirse en tiempo de ejecución:

Salida:

19.00
11.50
0

Un país desconocido recibe una tasa de 0m. Un producto decimal tiene tantas cifras decimales como sus dos operandos juntos, por eso 100m * 0.19m imprime 19.00 y la tasa cero imprime 0.

Expresión switch frente a sentencia switch

sentencia switchexpresión switch
Disponible desdeC# 1C# 8
Produce un valornosí
Sintaxis por ramacase X: ... break;X => value,
Alternativa finaldefault:_ =>
Varias sentencias por ramasíno, llama a un método
Aviso de exhaustividadnosí (CS8509)
Sin coincidencia en ejecuciónno se ejecuta nadaSwitchExpressionException

Las dos formas admiten los mismos patrones (tipos, constantes, propiedades, tuplas, relacionales), así que aprender una enseña la otra. La elección depende de la forma: un valor pasa por la expresión, una secuencia de acciones pasa por la sentencia.

Preguntas frecuentes

¿Qué es una expresión switch en C#?

Una expresión switch, añadida en C# 8, se evalúa a un valor: string text = code switch { 200 => "OK", 404 => "Not Found", _ => "Unknown" };. La entrada va antes de la palabra clave switch, cada brazo es pattern => result, los brazos se separan con comas y no hay palabras clave case, break ni default.

¿Cuál es el caso por defecto en una expresión switch de C#?

El patrón de descarte _ coincide con cualquier cosa, así que _ => "Unknown" como último brazo cumple el papel de default. Sin un brazo que lo recoja todo, el compilador avisa (CS8509) cuando alguna entrada no está cubierta, y un valor sin coincidencia en tiempo de ejecución lanza SwitchExpressionException.

¿Cómo hago coincidir varios valores en un brazo de una expresión switch?

Usa el patrón or de C# 9: "sat" or "sun" => "weekend". Para rangos, combina patrones relacionales con and: >= 13 and <= 19 => "teen". En C# 8 tendrías que escribir los valores como brazos separados con el mismo resultado.

¿Puedo usar when en una expresión switch de C#?

Sí. Un brazo puede añadir una guarda después de su patrón: Order o when o.Total > 100 => 0m. El brazo coincide solo si el patrón coincide y la condición when es verdadera; si no, la evaluación sigue con el brazo siguiente.

¿Debo usar una expresión switch o una sentencia switch?

Usa la expresión cuando cada rama solo produce un valor, como asociar un estado a una etiqueta o calcular un precio. Usa la sentencia cuando las ramas ejecutan varias sentencias, tienen efectos secundarios o necesitan salir de un bucle. La expresión además te da avisos de exhaustividad que la sentencia no da.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR