El pattern matching comprueba un valor contra una forma: "¿es un Circle?", "¿es null?", "¿es su Status "paid" y su Total mayor que 100?". Cuando la prueba tiene éxito, el patrón también puede extraer partes del valor en variables nuevas. Empezó siendo pequeño en C# 7 con el operador is y ha crecido en cada versión desde entonces, así que esta página indica la versión de cada forma.
El patrón de tipo con is (C# 7)
Antes de C# 7, comprobar un tipo y usarlo llevaba dos pasos: as más una comprobación de null, o is más un cast. El patrón de tipo hace las dos cosas a la vez y te da una variable con tipo:
Salida:
card ending 4242: 49.90
transfer from PT50...: 1200
no payment
other payment: 5
p is CardPayment card es verdadero solo cuando p es un CardPayment no null (o de una clase derivada de él), y entonces card lo contiene con el tipo estático correcto. Un valor null nunca coincide con un patrón de tipo, y por eso el pago null llega hasta la comprobación de null.
Ámbito y asignación definitiva
La variable del patrón existe en el bloque que la contiene, pero el compilador solo te deja leerla donde se sabe que la coincidencia tuvo éxito. Eso hace que funcione la forma de "cláusula de guarda", en la que sales pronto y usas la variable después:
Salida:
19.99
42 is over 40
price must be text
boxed is int n && n > 40 muestra la otra forma habitual: la variable se puede usar a la derecha de &&, porque ese lado solo se ejecuta cuando la coincidencia tuvo éxito. A la derecha de || no está asignada, y el compilador lo dice (CS0165).
Los patrones también hacen unboxing: boxed is int n tiene éxito con un int con boxing y te da el valor sin un cast que pueda lanzar una excepción. Con los tipos de valor nullable, maybe is int n tiene éxito exactamente cuando maybe tiene un valor.
Patrones de constante y de null
Una constante también es un patrón. x is null es el más usado, y C# 7 también permite x is 0, status is "paid" o un miembro de un enum:
if (customer is null) return; // null check that ignores any == overload
if (count is 0) Console.WriteLine("empty");
if (level is LogLevel.Error) Alert();
Muchos equipos prefieren is null a == null porque no puede desviarlo una sobrecarga de operador del tipo. C# 9 añade la forma negada, is not null.
Patrones en las sentencias switch (C# 7)
Los mismos patrones de tipo pueden servir de etiqueta a los case, y una cláusula when añade una condición. Los cases se prueban de arriba abajo, así que ordénalos de lo específico a lo general:
static string Describe(object o)
{
switch (o)
{
case null: return "nothing";
case int n when n < 0: return "negative number";
case int n: return $"number {n}";
case string s when s.Length == 0: return "empty text";
case string s: return $"text of {s.Length} chars";
case IEnumerable<int> list: return "a sequence of numbers";
default: return o.GetType().Name;
}
}
Describe(-4); // negative number
Describe(""); // empty text
Describe(new List<int>()); // a sequence of numbers
Describe(2.5); // Double
El compilador comprueba el orden: un case que nunca puede coincidir porque uno anterior lo cubre es el error CS8120. default siempre se evalúa el último, esté donde esté. La misma lógica escrita con cadenas if/is, como en el ejemplo de los pagos de arriba, funciona en todas las versiones de C# desde la 7.
Expresiones switch (C# 8)
Una expresión switch es la forma compacta de "calcular un valor a partir de varios casos". Primero va el valor, después switch y después los brazos pattern => result separados por comas, con _ como caso comodín:
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;
static double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle { Width: var w, Height: var h } => w * h,
_ => throw new ArgumentException("Unknown shape", nameof(shape)),
};
Area(new Rectangle(3, 4)); // 12
Si ningún brazo coincide en tiempo de ejecución, lanza SwitchExpressionException, y el compilador avisa (CS8509) cuando puede ver que alguna entrada no se maneja. La página de la expresión switch trata la sintaxis por sí sola; el resto de esta página trata los patrones que puedes poner en los brazos.
Patrones de propiedad (C# 8, ampliados en C# 10)
Un patrón de propiedad compara las propiedades de un objeto con patrones anidados, entre llaves. Además, el objeto no debe ser null, así que { } por sí solo significa "no null":
public record Address(string City, string Country);
public record Order(decimal Total, string Status, Address ShipTo, int Items);
static string Shipping(Order order) => order switch
{
{ Status: "cancelled" } => "no shipment",
{ ShipTo.Country: "PT", Total: >= 50m } => "free, domestic", // C# 10 dotted form
{ ShipTo.Country: "PT" } => "4.90, domestic",
{ Total: > 200m } => "free, international",
_ => "12.00, international",
};
Shipping(new Order(60m, "paid", new Address("Porto", "PT"), 2)); // free, domestic
Shipping(new Order(260m, "paid", new Address("Lyon", "FR"), 2)); // free, international
En C# 8 y 9 hace falta la forma anidada: { ShipTo: { Country: "PT" } }. C# 10 permite la forma abreviada con puntos que se usa arriba. Los patrones de propiedad también funcionan con is, lo que hace que las comprobaciones con varias condiciones se lean como una descripción: if (order is { Status: "paid", Items: > 0 }).
Patrones relacionales y lógicos (C# 9)
C# 9 añadió las comparaciones (<, <=, >, >=) y los combinadores and, or y not, que convierten los rangos en patrones:
static string Grade(int score) => score switch
{
< 0 or > 100 => "invalid",
>= 90 => "A",
>= 75 and < 90 => "B",
>= 50 => "C",
_ => "F",
};
Grade(82); // B
Grade(101); // invalid
if (input is not null and not "") { ... }
if (c is >= 'a' and <= 'z' or >= 'A' and <= 'Z') { ... } // and binds tighter than or
El equivalente en C# 7 es una cadena if/else if o un switch con cláusulas when (case int s when s >= 90:).
Patrones de tupla y posicionales (C# 8)
Hacer switch sobre varios valores a la vez se hace haciendo switch sobre una tupla:
static string Quadrant(int x, int y) => (x, y) switch
{
(0, 0) => "origin",
(> 0, > 0) => "I",
(< 0, > 0) => "II",
(< 0, < 0) => "III",
(> 0, < 0) => "IV",
_ => "on an axis",
};
Quadrant(-2, 5); // II
Un patrón posicional hace lo mismo con cualquier tipo que tenga un método Deconstruct, incluidos los records: case Point(0, 0): o p is Rectangle(var w, var h) && w == h.
Patrones de lista (C# 11)
Los patrones de lista comprueban arrays y listas por sus elementos. .. coincide con cualquier número de elementos (un "slice"), y cada posición de elemento puede contener cualquier otro patrón:
static string Route(string[] args) => args switch
{
[] => "help",
["add", var item] => $"add {item}",
["remove", var item, ..] => $"remove {item}",
[var cmd, ..] => $"unknown command {cmd}",
};
Route(new[] { "add", "milk" }); // add milk
Route(new[] { "remove", "milk", "now" }); // remove milk
int[] nums = { 1, 2, 3, 4 };
if (nums is [1, .., var last]) Console.WriteLine(last); // 4
Qué versión añadió qué
| Patrón | Ejemplo | Versión |
|---|---|---|
| Patrón de tipo con variable | o is Circle c | C# 7.0 |
| Patrón de constante | x is null, n is 0 | C# 7.0 |
Patrón var | x is var v | C# 7.0 |
Patrones en etiquetas case, guardas when | case int n when n < 0: | C# 7.0 |
Expresión switch, descarte _ | x switch { ... } | C# 8 |
| Patrón de propiedad | { Status: "paid" } | C# 8 |
| Patrón de tupla | (x, y) switch { (0, 0) => ... } | C# 8 |
| Patrón posicional | Point(0, 0) | C# 8 |
| Patrón relacional | > 100, <= 0 | C# 9 |
| Patrones lógicos | not null, >= 1 and <= 5, 'a' or 'b' | C# 9 |
| Patrón de tipo sin variable | case Circle:, Circle => ... | C# 9 |
| Patrón de propiedad extendido | { ShipTo.City: "Porto" } | C# 10 |
| Patrón de lista y slice | [first, .., last] | C# 11 |
Errores comunes
- Ordenar los cases de lo general a lo específico.
case Payment p:antes decase CardPayment c:hace inalcanzable el segundo (CS8120). Lo específico primero. - Esperar que un patrón de tipo coincida con null.
null is string ses falso. Manejanullcon su propio case o conis null. - Leer la variable del patrón donde la coincidencia puede haber fallado. Después de
a is int n || ...,nno está asignada (CS0165). - Switches largos por tipo en lugar de métodos virtuales. Si cada subclase nueva obliga a editar el mismo switch en cinco sitios, el comportamiento pertenece a la jerarquía de clases. Los patrones brillan con datos que vienen de fuera (formas de JSON, mensajes, tuplas de entradas) y con conjuntos cerrados de tipos que controlas.
- Olvidar el caso comodín en una expresión switch. Una entrada sin coincidencia lanza una excepción en tiempo de ejecución; lee el aviso CS8509.
Preguntas frecuentes
¿Qué es el pattern matching en C#?
El pattern matching comprueba un valor contra una forma y, cuando coincide, puede extraer partes de él en variables en el mismo paso. if (shape is Circle c) comprueba el tipo y te da un c con tipo; order is { Status: "paid", Total: > 100 } comprueba propiedades. Los patrones aparecen en las expresiones is, en las sentencias switch y en las expresiones switch.
¿Cómo uso is con una variable en C#?
if (obj is Customer c) { ... } comprueba que obj es un Customer (no null) y lo asigna a c, con tipo Customer, dentro del if. Sustituye a la antigua pareja var c = obj as Customer; if (c != null). La variable solo está definitivamente asignada donde se sabe que la prueba es verdadera, así que if (!(obj is Customer c)) return; deja c utilizable después del if.
¿Qué diferencia hay entre is null y == null en C#?
x is null siempre comprueba la propia referencia. x == null llama al operador == del tipo si lo sobrecarga, lo que normalmente da la misma respuesta pero no está garantizado. Para lo contrario, escribe x != null, !(x is null) o, desde C# 9, x is not null.
¿Cómo funciona el pattern matching en un switch con when en C#?
Una etiqueta case puede contener un patrón más una guarda when: case Order o when o.Total > 100:. El case coincide solo si el patrón coincide y la condición es verdadera, y los cases se prueban de arriba abajo, así que pon primero los más específicos. El compilador da CS8120 cuando un case nunca puede alcanzarse porque uno anterior ya lo cubre.
¿Qué versión de C# añadió cada patrón?
C# 7.0: patrones de tipo, de constante y var en is y en case, además de when. C# 8: expresiones switch y patrones de propiedad, de tupla y posicionales. C# 9: patrones relacionales (> 5), lógicos (and, or, not) y de tipo sin variable. C# 10: patrones de propiedad extendidos ({ Address.City: "Porto" }). C# 11: patrones de lista ([1, .., var last]).