Coincidencia de modelosPattern Matching

Los patrones comprueban que un valor tenga una determinada forma y pueden extraer información del valor cuando tiene la forma coincidente.Patterns test that a value has a certain shape, and can extract information from the value when it has the matching shape. La coincidencia de patrones proporciona una sintaxis más concisa para los algoritmos que se usan actualmente.Pattern matching provides more concise syntax for algorithms you already use today. Ya se crean algoritmos de coincidencia de patrones mediante la sintaxis existente.You already create pattern matching algorithms using existing syntax. Se escriben instrucciones if o switch que comprueban valores.You write if or switch statements that test values. Luego, si esas instrucciones coinciden, se extrae y se usa la información de ese valor.Then, when those statements match, you extract and use information from that value. Los nuevos elementos de sintaxis son extensiones de instrucciones con las que ya está familiarizado: is y switch.The new syntax elements are extensions to statements you're already familiar with: is and switch. Estas nuevas extensiones combinan la comprobación de un valor y la extracción de esa información.These new extensions combine testing a value and extracting that information.

En este artículo se tratará la nueva sintaxis para mostrar cómo permite escribir un código conciso y legible.In this article, we'll look at the new syntax to show you how it enables readable, concise code. La coincidencia de patrones permite expresiones donde se separan el código y los datos, a diferencia de los diseños orientados a objetos, donde los datos y los métodos que los manipulan están estrechamente unidos.Pattern matching enables idioms where data and the code are separated, unlike object-oriented designs where data and the methods that manipulate them are tightly coupled.

Para mostrar estas nuevas expresiones, vamos a trabajar con estructuras que representan formas geométricas mediante instrucciones de coincidencia de patrones.To illustrate these new idioms, let's work with structures that represent geometric shapes using pattern matching statements. Probablemente esté familiarizado con la creación de jerarquías de clases y de métodos virtuales y métodos invalidados para personalizar el comportamiento de los objetos según el tipo de tiempo de ejecución del objeto.You're probably familiar with building class hierarchies and creating virtual methods and overridden methods to customize object behavior based on the runtime type of the object.

Esas técnicas no son posibles en el caso de los datos que no están estructurados en una jerarquía de clases.Those techniques aren't possible for data that isn't structured in a class hierarchy. Cuando los datos y los métodos están separados, se necesitan otras herramientas.When data and methods are separate, you need other tools. Las nuevas construcciones de coincidencia de patrones permiten una sintaxis más limpia para examinar los datos y manipular el flujo de control basándose en cualquier condición de esos datos.The new pattern matching constructs enable cleaner syntax to examine data and manipulate control flow based on any condition of that data. Ya escribe instrucciones if y switch que comprueban el valor de una variable.You already write if statements and switch that test a variable's value. Escribe instrucciones is que comprueban el tipo de una variable.You write is statements that test a variable's type. La coincidencia de patrones agrega nuevas capacidades a esas instrucciones.Pattern matching adds new capabilities to those statements.

En este artículo se creará un método que calcula el área de distintas formas geométricas.In this article, you'll build a method that computes the area of different geometric shapes. Pero se hará sin recurrir a técnicas orientadas a objetos y sin crear una jerarquía de clases para las diferentes formas.But, you'll do it without resorting to object-oriented techniques and building a class hierarchy for the different shapes. En lugar de esto se usará la coincidencia de patrones.You'll use pattern matching instead. Conforme avance en este ejemplo, compare este código con cómo se estructuraría como una jerarquía de objetos.As you go through this sample, contrast this code with how it would be structured as an object hierarchy. Cuando los datos que se deben consultar y manipular no son una jerarquía de clases, la coincidencia de patrones permite diseños elegantes.When the data you must query and manipulate isn't a class hierarchy, pattern matching enables elegant designs.

En lugar de empezar con una definición de forma abstracta y agregar diferentes clases de formas concretas, se comenzará con simples definiciones solo de datos para cada una de las formas geométricas:Rather than starting with an abstract shape definition and adding different specific shape classes, let's start instead with simple data only definitions for each of the geometric shapes:

public class Square
{
    public double Side { get; }

    public Square(double side)
    {
        Side = side;
    }
}
public class Circle
{
    public double Radius { get; }

    public Circle(double radius)
    {
        Radius = radius;
    }
}
public struct Rectangle
{
    public double Length { get; }
    public double Height { get; }

    public Rectangle(double length, double height)
    {
        Length = length;
        Height = height;
    }
}
public class Triangle
{
    public double Base { get; }
    public double Height { get; }

    public Triangle(double @base, double height)
    {
        Base = @base;
        Height = height;
    }
}

A partir de estas estructuras se va a escribir un método que calcula el área de alguna forma.From these structures, let's write a method that computes the area of some shape.

Expresión de patrón de tipo isThe is type pattern expression

Antes de C# 7.0, había que comprobar cada tipo en una serie de instrucciones if e is:Before C# 7.0, you'd need to test each type in a series of if and is statements:

public static double ComputeArea(object shape)
{
    if (shape is Square)
    {
        var s = (Square)shape;
        return s.Side * s.Side;
    } 
    else if (shape is Circle)
    {
        var c = (Circle)shape;
        return c.Radius * c.Radius * Math.PI;
    }
    // elided
    throw new ArgumentException(
        message: "shape is not a recognized shape",
        paramName: nameof(shape));
}

El código anterior es una expresión clásica del patrón de tipo: se prueba una variable para determinar su tipo y se realiza una acción diferente basada en ese tipo.That code above is a classic expression of the type pattern: You're testing a variable to determine its type and taking a different action based on that type.

Este código se simplifica con extensiones de la expresión is para asignar una variable si la prueba se realiza correctamente:This code becomes simpler using extensions to the is expression to assign a variable if the test succeeds:

public static double ComputeAreaModernIs(object shape)
{
    if (shape is Square s)
        return s.Side * s.Side;
    else if (shape is Circle c)
        return c.Radius * c.Radius * Math.PI;
    else if (shape is Rectangle r)
        return r.Height * r.Length;
    // elided
    throw new ArgumentException(
        message: "shape is not a recognized shape",
        paramName: nameof(shape));
}

En esta versión actualizada, la expresión is prueba la variable y la asigna a una nueva variable del tipo correcto.In this updated version, the is expression both tests the variable and assigns it to a new variable of the proper type. Observe también que esta versión incluye el tipo Rectangle, que es un elemento struct.Also, notice that this version includes the Rectangle type, which is a struct. La nueva expresión is funciona con tipos de valor y con tipos de referencia.The new is expression works with value types as well as reference types.

Las reglas del lenguaje para las expresiones de coincidencia de patrones ayudan a evitar el uso indebido de los resultados de una expresión de coincidencia.Language rules for pattern matching expressions help you avoid misusing the results of a match expression. En el ejemplo anterior, las variables s, c y r solo están en el ámbito y se asignan definitivamente cuando las expresiones de coincidencia de patrones respectivas tienen resultados true.In the example above, the variables s, c, and r are only in scope and definitely assigned when the respective pattern match expressions have true results. Si intenta usar una de las variables en otra ubicación, el código genera errores del compilador.If you try to use either variable in another location, your code generates compiler errors.

Vamos a examinar detenidamente esas dos reglas, a partir del ámbito.Let's examine both of those rules in detail, beginning with scope. La variable c está en el ámbito únicamente en la rama else de la primera instrucción if.The variable c is in scope only in the else branch of the first if statement. La variable s está en el ámbito en el método ComputeAreaModernIs.The variable s is in scope in the method ComputeAreaModernIs. Eso se debe a que cada rama de una instrucción if establece un ámbito independiente para las variables.That's because each branch of an if statement establishes a separate scope for variables. Pero la propia instrucción if no.However, the if statement itself doesn't. Eso significa que las variables declaradas en la instrucción if están en el mismo ámbito que la instrucción if (el método en este caso). Este comportamiento no es específico de la coincidencia de patrones, sino que es el definido para los ámbitos de variable y las instrucciones if y else.That means variables declared in the if statement are in the same scope as the if statement (the method in this case.) This behavior isn't specific to pattern matching, but is the defined behavior for variable scopes and if and else statements.

Las variables c y s se asignan cuando las respectivas instrucciones if son true debido al mecanismo when true asignado definitivamente.The variables c and s are assigned when the respective if statements are true because of the definitely assigned when true mechanism.

Sugerencia

En los ejemplos de este tema se usa la construcción recomendada, donde una expresión de coincidencia de patrones is asigna definitivamente la variable de coincidencia en la rama true de la instrucción if.The samples in this topic use the recommended construct where a pattern match is expression definitely assigns the match variable in the true branch of the if statement. Se podría invertir la lógica al decir if (!(shape is Square s)) y la variable s se asignaría definitivamente solo en la rama false.You could reverse the logic by saying if (!(shape is Square s)) and the variable s would be definitely assigned only in the false branch. Aunque esto es C# válido, no se recomienda, porque es más confuso para seguir la lógica.While this is valid C#, it is not recommended because it is more confusing to follow the logic.

Estas reglas significan que es poco probable que se acceda accidentalmente al resultado de una expresión de coincidencia de patrones cuando no ha habido coincidencia de ese patrón.These rules mean that you're unlikely to accidentally access the result of a pattern match expression when that pattern wasn't met.

Uso de instrucciones de coincidencia de patrones switchUsing pattern matching switch statements

Con el tiempo, es posible que tenga que admitir otros tipos de formas.As time goes on, you may need to support other shape types. A medida que crece el número de condiciones que se está probando, puede resultar pesado el uso de expresiones de coincidencia de patrones is.As the number of conditions you're testing grows, you'll find that using the is pattern matching expressions can become cumbersome. Además de necesitar instrucciones if en cada tipo que se quiere comprobar, las expresiones is solo se pueden probar si la entrada coincide con un único tipo.In addition to requiring if statements on each type you want to check, the is expressions are limited to testing if the input matches a single type. En este caso, las expresiones de coincidencia de patrones switch son una mejor opción.In this case, you'll find that the switch pattern matching expressions becomes a better choice.

La instrucción tradicional switch era una expresión de patrón: admitía el patrón de constante.The traditional switch statement was a pattern expression: it supported the constant pattern. Se podía comparar una variable con cualquier constante usada en una instrucción case:You could compare a variable to any constant used in a case statement:

public static string GenerateMessage(params string[] parts)
{
    switch (parts.Length)
    {
        case 0:
            return "No elements to the input";
        case 1:
            return $"One element: {parts[0]}";
        case 2:
            return $"Two elements: {parts[0]}, {parts[1]}";
        default:
            return $"Many elements. Too many to write";
    }
}

El único patrón admitido por la instrucción switch era el patrón de constante.The only pattern supported by the switch statement was the constant pattern. Se limitaba aún más a tipos numéricos y al tipo string.It was further limited to numeric types and the string type. Esas restricciones se han eliminado y ahora se puede escribir una instrucción switch con el patrón de tipos:Those restrictions have been removed, and you can now write a switch statement using the type pattern:

public static double ComputeAreaModernSwitch(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Rectangle r:
            return r.Height * r.Length;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

La instrucción de coincidencia de patrones switch usa una sintaxis familiar para los desarrolladores que han empleado la instrucción de estilo C tradicional switch.The pattern matching switch statement uses familiar syntax to developers who have used the traditional C-style switch statement. Cada case se evalúa y se ejecuta el código debajo de la condición que coincide con la variable de entrada.Each case is evaluated and the code beneath the condition that matches the input variable is executed. La ejecución de código no puede "pasar explícitamente" de una expresión case a la siguiente; la sintaxis de la instrucción case exige que cada case termine con break, return o goto.Code execution can't "fall through" from one case expression to the next; the syntax of the case statement requires that each case end with a break, return, or goto.

Nota

Las instrucciones goto para saltar a otra etiqueta solo son válidas para el patrón de constante, la instrucción switch clásica.The goto statements to jump to another label are valid only for the constant pattern (the classic switch statement).

Hay nuevas e importantes reglas que rigen la instrucción switch.There are important new rules governing the switch statement. Las restricciones respecto al tipo de la variable en la expresión switch se han eliminado.The restrictions on the type of the variable in the switch expression have been removed. Se puede usar cualquier tipo, como object en este ejemplo.Any type, such as object in this example, may be used. Las expresiones case ya no se limitan a valores constantes.The case expressions are no longer limited to constant values. La eliminación de esa limitación significa que la reordenación de secciones switch puede cambiar el comportamiento de un programa.Removing that limitation means that reordering switch sections may change a program's behavior.

Cuando se limitaba a valores constantes, más de una etiqueta case podía coincidir con el valor de la expresión switch.When limited to constant values, no more than one case label could match the value of the switch expression. Eso sumado a la regla de que cada sección switch no debe pasar explícitamente a la sección siguiente, el resultado era que las secciones switch se podían reorganizar en cualquier orden sin afectar al comportamiento.Combine that with the rule that every switch section must not fall through to the next section, and it followed that the switch sections could be rearranged in any order without affecting behavior. Ahora, con expresiones switch más generalizadas, el orden de cada sección importa.Now, with more generalized switch expressions, the order of each section matters. Las expresiones switch se evalúan en orden textual.The switch expressions are evaluated in textual order. La ejecución se transfiere a la primera etiqueta switch que coincide con la expresión switch.Execution transfers to the first switch label that matches the switch expression.
El caso default solo se ejecutará si no coincide ninguna otra etiqueta case.The default case will only be executed if no other case labels match. El caso default se evalúa en último lugar, independientemente de su orden textual.The default case is evaluated last, regardless of its textual order. Si no hay ningún caso default y ninguna de las instrucciones case coincide, la ejecución continúa en la instrucción siguiente a la instrucción switch.If there's no default case, and none of the other case statements match, execution continues at the statement following the switch statement. No se ejecuta el código de ninguna de las etiquetas case.None of the case labels code is executed.

Cláusulas when en expresiones casewhen clauses in case expressions

Puede crear casos especiales para las formas que tengan área 0 mediante una cláusula when en la etiqueta case.You can make special cases for those shapes that have 0 area by using a when clause on the case label. Un cuadrado con una longitud de lado de 0 o un círculo con un radio de 0 tiene un área 0.A square with a side length of 0, or a circle with a radius of 0 has a 0 area. Esa condición se especifica mediante una cláusula when en la etiqueta case:You specify that condition using a when clause on the case label:

public static double ComputeArea_Version3(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
            return 0;

        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

Este cambio muestra algunos puntos importantes sobre la nueva sintaxis.This change demonstrates a few important points about the new syntax. En primer lugar, se pueden aplicar varias etiquetas case a una sección switch.First, multiple case labels can be applied to one switch section. El bloque de instrucciones se ejecuta cuando cualquiera de esas etiquetas es true.The statement block is executed when any of those labels is true. En esta instancia, si la expresión switch es un círculo o un cuadrado con área 0, el método devuelve la constante 0.In this instance, if the switch expression is either a circle or a square with 0 area, the method returns the constant 0.

Este ejemplo presenta dos variables distintas en las dos etiquetas case del primer bloque switch.This example introduces two different variables in the two case labels for the first switch block. Observe que las instrucciones de este bloque switch no usan las variables c (para el círculo) ni s (para el cuadrado).Notice that the statements in this switch block don't use either the variables c (for the circle) or s (for the square). Ninguna de esas variables se ha asignado definitivamente en este bloque switch.Neither of those variables is definitely assigned in this switch block. Si alguno de estos casos coincide, claramente se ha asignado una de las variables.If either of these cases match, clearly one of the variables has been assigned. Pero no es posible saber cuál se ha asignado en tiempo de compilación, ya que cualquiera de los casos podría coincidir en tiempo de ejecución.However, it's impossible to tell which has been assigned at compile time, because either case could match at runtime. Por ese motivo, la mayoría de las veces en que se usan varias etiquetas case para el mismo bloque, no se presenta una nueva variable en la instrucción case o solo se usará la variable en la cláusula when.For that reason, most times when you use multiple case labels for the same block, you won't introduce a new variable in the case statement, or you'll only use the variable in the when clause.

Una vez agregadas esas formas con área 0, se van a agregar un par de tipos de formas más: un rectángulo y un triángulo:Having added those shapes with 0 area, let's add a couple more shape types: a rectangle and a triangle:

public static double ComputeArea_Version4(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
        case Triangle t when t.Base == 0 || t.Height == 0:
        case Rectangle r when r.Length == 0 || r.Height == 0:
            return 0;

        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Triangle t:
            return t.Base * t.Height / 2;
        case Rectangle r:
            return r.Length * r.Height;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

Este conjunto de cambios agrega etiquetas case para el caso degenerado y etiquetas y bloques para cada una de las nuevas formas.This set of changes adds case labels for the degenerate case, and labels and blocks for each of the new shapes.

Por último, puede agregar un caso null para garantizar que el argumento no sea null:Finally, you can add a null case to ensure the argument isn't null:

public static double ComputeArea_Version5(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
        case Triangle t when t.Base == 0 || t.Height == 0:
        case Rectangle r when r.Length == 0 || r.Height == 0:
            return 0;

        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Triangle t:
            return t.Base * t.Height / 2;
        case Rectangle r:
            return r.Length * r.Height;
        case null:
            throw new ArgumentNullException(paramName: nameof(shape), message: "Shape must not be null");
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

El comportamiento especial del patrón null es interesante porque la constante null del patrón no tiene un tipo, pero se puede convertir a cualquier tipo de referencia o tipo que acepte valores NULL.The special behavior for the null pattern is interesting because the constant null in the pattern doesn't have a type but can be converted to any reference type or nullable type. En lugar de convertir null en cualquier tipo, el lenguaje define que un valor null no coincidirá con ningún patrón de tipo, independientemente del tipo de tiempo de compilación de la variable.Rather than convert a null to any type, the language defines that a null value won't match any type pattern, regardless of the compile-time type of the variable. Este comportamiento hace que el nuevo patrón de tipo basado en switch sea coherente con la instrucción is: las instrucciones is siempre devuelven false cuando el valor que se está comprobando es null.This behavior makes the new switch based type pattern consistent with the is statement: is statements always return false when the value being checked is null. También es más sencillo: una vez que haya comprobado el tipo, no necesita una comprobación de NULL adicional.It's also simpler: once you've checked the type, you don't need an additional null check. Puede comprobar esto en que no existen comprobaciones de NULL en ninguno de los bloques de casos de los ejemplos anteriores: no son necesarias, ya que la coincidencia del patrón de tipo garantiza un valor distinto de NULL.You can see that from the fact that there are no null checks in any of the case blocks of the samples above: they aren't necessary, since matching the type pattern guarantees a non-null value.

Declaraciones var en expresiones casevar declarations in case expressions

La introducción de var como una de las expresiones de coincidencia presenta nuevas reglas para la coincidencias de patrones.The introduction of var as one of the match expressions introduces new rules to the pattern match.

La primera regla es que la declaración var sigue las reglas de inferencia de tipo normal: El tipo se infiere para que sea el tipo estático de la expresión switch.The first rule is that the var declaration follows the normal type inference rules: The type is inferred to be the static type of the switch expression. De esa regla, el tipo siempre coincide.From that rule, the type always matches.

La segunda regla es que una declaración var no tiene la comprobación de valores NULL que incluyen otras expresiones de patrón de tipo.The second rule is that a var declaration doesn't have the null check that other type pattern expressions include. Esto significa que la variable puede ser NULL y se necesita una comprobación de valores NULL en ese caso.That means the variable may be null, and a null check is necessary in that case.

Esas dos reglas implican que, en muchos casos, una declaración var en una expresión case coincide con las mismas condiciones que una expresión default.Those two rules mean that in many instances, a var declaration in a case expression matches the same conditions as a default expression. Dado que se prefiere cualquier caso que no sea default al caso default, el caso default nunca se ejecutará.Because any non-default case is preferred to the default case, the default case will never execute.

Nota

El compilador no emite una advertencia en esos casos en que se ha escrito un caso default pero nunca se ejecutará.The compiler does not emit a warning in those cases where a default case has been written but will never execute. Esto es coherente con el comportamiento actual de la instrucción switch donde se han enumerado todos los casos posibles.This is consistent with current switch statement behavior where all possible cases have been listed.

La tercera regla presenta usos donde un caso var puede resultar útil.The third rule introduces uses where a var case may be useful. Imagine que va a realizar una coincidencia de patrones donde la entrada es una cadena y busca valores de comando conocidos.Imagine that you're doing a pattern match where the input is a string and you're searching for known command values. Podría escribir algo parecido a esto:You might write something like:

static object CreateShape(string shapeDescription)
{
    switch (shapeDescription)
    {
        case "circle":
            return new Circle(2);

        case "square":
            return new Square(4);
        
        case "large-circle":
            return new Circle(12);

        case var o when (o?.Trim().Length ?? 0) == 0:
            // white space
            return null;
        default:
            return "invalid shape description";
    }            
}

El caso var coincide con null, la cadena vacía, o cualquier cadena que contenga solo espacios en blanco.The var case matches null, the empty string, or any string that contains only white space. Tenga en cuenta que el código anterior usa el operador ?. para asegurarse de que no genera por accidente una NullReferenceException.Notice that the preceding code uses the ?. operator to ensure that it doesn't accidentally throw a NullReferenceException. El caso default controla cualquier otro valor de cadena que no entienda este analizador de comandos.The default case handles any other string values that aren't understood by this command parser.

Este es un ejemplo en que puede que quiera considerar el uso de una expresión de caso var que sea distinta de una expresión default.This is one example where you may want to consider a var case expression that is distinct from a default expression.

ConclusionesConclusions

Las construcciones de coincidencia de patrones permiten administrar fácilmente el flujo de control entre distintas variables y tipos que no están relacionados mediante una jerarquía de herencia.Pattern Matching constructs enable you to easily manage control flow among different variables and types that aren't related by an inheritance hierarchy. También se puede controlar la lógica para usar cualquier condición que se pruebe en la variable.You can also control logic to use any condition you test on the variable. Permite patrones y expresiones que se van a necesitar más a menudo a medida que se crean aplicaciones más distribuidas, donde los datos y los métodos que los manipulan están separados.It enables patterns and idioms that you'll need more often as you build more distributed applications, where data and the methods that manipulate that data are separate. Observará que las estructuras de forma usadas en este ejemplo no contienen métodos, solo propiedades de solo lectura.You'll notice that the shape structs used in this sample don't contain any methods, just read-only properties. La coincidencia de patrones funciona con cualquier tipo de datos.Pattern Matching works with any data type. Se escriben expresiones que examinan el objeto y se toman decisiones de flujo de control basadas en esas condiciones.You write expressions that examine the object, and make control flow decisions based on those conditions.

Compare el código de este ejemplo con el diseño que se obtendría al crear una jerarquía de clases para un elemento Shape abstracto y formas derivadas concretas cada una con su propia implementación de un método virtual para calcular el área.Compare the code from this sample with the design that would follow from creating a class hierarchy for an abstract Shape and specific derived shapes each with their own implementation of a virtual method to calculate the area. A menudo encontrará que las expresiones de coincidencia de patrones pueden ser una herramienta muy útil al trabajar con datos y querer separar las preocupaciones sobre almacenamiento de datos de las preocupaciones sobre comportamiento.You'll often find that pattern matching expressions can be a very useful tool when you're working with data and want to separate the data storage concerns from the behavior concerns.