11 Patrones y coincidencia de patrones

11.1 General

Se puede usar un patrón con el is operador (§12.15.12), en una switch_statement (§13.8.3) y en un switch_expression (§12.12) para describir la forma de datos con los que se van a comparar los datos entrantes. Los patrones se pueden anidar, con partes de los datos que se comparan con subprocesos.

Un patrón se prueba con un valor en varios contextos:

  • En un switch_statement, el patrón de un switch_label se prueba con el selector_expression del switch_statement.
  • Con un operador is-pattern , el patrón del lado derecho se prueba con la expresión de la izquierda.
  • En un switch_expression, el patrón de un switch_expression_arm se prueba con la expresión en el lado izquierdo del switch_expression.
  • En contextos anidados, el subproceso se prueba con valores recuperados de propiedades, campos o indexados de otros valores de entrada, según el formulario de patrón.

El valor con el que se prueba un patrón se denomina valor de entrada de patrón.

11.2 Formularios de patrón

11.2.1 General

Un patrón puede tener una de las formas siguientes:

pattern
    : logical_pattern
    ;

primary_pattern
    : parenthesized_pattern
    | declaration_pattern
    | constant_pattern
    | var_pattern
    | positional_pattern
    | property_pattern
    | discard_pattern
    | type_pattern
    | relational_pattern
    | logical_pattern
    | list_pattern
    | slice_pattern
    ;

La '(' pattern ')' producción permite incluir un patrón entre paréntesis para aplicar el orden de evaluación entre patrones combinados mediante uno de los logical_patterns.

Si la entrada se puede reconocer sintácticamente como una constant_pattern y una positional_pattern , se elegirá el constant_pattern .

Algunos patronespueden dar lugar a la declaración de una variable local.

Cada formulario de patrón define el conjunto de tipos para los valores de entrada a los que se puede aplicar el patrón. Un patrón P es aplicable a un tipo T si T está entre los tipos cuyos valores puede coincidir el patrón. Es un error en tiempo de compilación si un patrón P aparece en un programa para que coincida con un valor de entrada de patrón (§11.1) de tipo T si P no es aplicable a T.

Ejemplo: el ejemplo siguiente genera un error en tiempo de compilación porque el tipo de tiempo de compilación de v es TextReader. Una variable de tipo TextReader nunca puede tener un valor compatible con string:

TextReader v = Console.In; // compile-time type of 'v' is 'TextReader'
if (v is string) // compile-time error
{
    // code assuming v is a string
}

Sin embargo, lo siguiente no genera un error en tiempo de compilación porque el tipo de tiempo de compilación de v es object. Una variable de tipo object podría tener un valor compatible con la referencia con string:

object v = Console.In;
if (v is string s)
{
    // code assuming v is a string
}

ejemplo final

Cada formulario de patrón define el conjunto de valores para los que el patrón coincide con el valor en tiempo de ejecución.

No se especifica el orden de evaluación de operaciones y efectos secundarios durante la coincidencia de patrones (llamadas a , accesos a Deconstructpropiedades e invocaciones de miembros de System.Runtime.CompilerServices.ITuple).

11.2.2 Patrón de declaración

Se usa un declaration_pattern para probar que un valor tiene un tipo determinado y, si la prueba se realiza correctamente, para proporcionar opcionalmente el valor en una variable de ese tipo.

declaration_pattern
    : type simple_designation
    ;
simple_designation
    : discard_designation
    | single_variable_designation
    ;
discard_designation
    : '_'
    ;
single_variable_designation
    : identifier
    ;

Al reconocer una simple_designation si se aplican tanto la discard_designation como single_variable_designation alternativas, se elegirá la primera.

Nota: ANTLR hace la elección especificada automáticamente debido a la ordenación de las alternativas de simple_designation. nota final

El tipo en tiempo de ejecución del valor se prueba con el tipo en el patrón mediante las mismas reglas especificadas en el operador is-type (§12.15.12.1). Si la prueba se realiza correctamente, el patrón coincide con ese valor. Es un error en tiempo de compilación si el tipo es un tipo de valor que acepta valores NULL (§8.3.12) o un tipo de referencia que acepta valores NULL (§8.9.3). Este formulario de patrón nunca coincide con un null valor.

Nota: La expresión e is T is-type y el patrón e is T _ de declaración son equivalentes cuando T no es un tipo que acepta valores NULL. nota final

Dado un valor de entrada de patrón (§11.1) e, si el simple_designation es un discard_designation, denotando un descarte (§9.2.9.2), el valor de e no está enlazado a nada. De lo contrario, si el simple_designation es un single_variable_designation, se introduce una variable local (§9.2.9) del tipo especificado denominado por el identificador especificado. A esa variable local se le asigna el valor del valor de entrada del patrón cuando el patrón coincide con el valor.

Nota: Este tratamiento de dentro de _ un declaration_pattern difiere del de un independiente _ escrito como patrón (§11.2.7): en este último caso, una constante en el ámbito o un tipo denominado _, si existe, no está oculto. nota final

Se dice que un tipo E es compatible con el tipo T si existe una conversión de identidad, una conversión de referencia implícita o explícita, una conversión de conversión boxing, una conversión de unboxing o una conversión implícita o explícita de tipo de valor que acepta valores NULL desde E a T, o si o ET es un tipo abierto (§8.4.3). Un patrón de declaración que asigna un nombre a un tipo T se aplica a (§11.2.1) cada tipo E para el que E sea compatible con Tel patrón . Se trata de un error en tiempo de compilación si se usa un patrón de declaración que asigna un nombre a un tipo T para coincidir con un valor de entrada de patrón (§11.1) cuyo tipo E estático no es compatible con el patrón .T

Nota: La compatibilidad con tipos abiertos puede ser más útil al comprobar tipos que pueden ser tipos de estructura o clase, y se debe evitar la conversión boxing. nota final

Ejemplo: el patrón de declaración es útil para realizar pruebas de tipos en tiempo de ejecución de tipos de referencia y reemplaza la expresión

var v = expr as Type;
if (v != null) { /* code using v */ }

con un poco más conciso

if (expr is Type v) { /* code using v */ }

ejemplo final

Ejemplo: El patrón de declaración se puede usar para probar valores de tipos que aceptan valores NULL: un valor de tipo Nullable<T> (o un boxed T) coincide con un patrón T2 id de tipo si el valor no es NULL y T2 es T, o algún tipo base o interfaz de T. Por ejemplo, en el fragmento de código

int? x = 3;
if (x is int v) { /* code using v */ }

La condición de la if instrucción está true en tiempo de ejecución y la variable v contiene el valor 3 de tipo int dentro del bloque. Después del bloque, la variable v está en el ámbito, pero no está asignada definitivamente. ejemplo final

11.2.3 Patrón constante

Un constant_pattern se usa para probar el valor de un valor de entrada de patrón (§11.1) con el valor constante especificado.

constant_pattern
    : constant_expression
    ;

Un patrón P de constante se aplica a un tipo T si hay una conversión implícita de la expresión constante de P al tipo T.

Para un patrón Pconstante, su valor convertido es

  • si el tipo del valor de entrada del patrón es un tipo entero o un tipo de enumeración, el valor constante del patrón se convierte en ese tipo; de otra manera
  • si el tipo del valor de entrada del patrón es la versión que acepta valores NULL de un tipo entero o un tipo de enumeración, el valor constante del patrón se convierte en su tipo subyacente; de otra manera
  • el valor del valor constante del patrón.

Dado un valor de entrada de patrón e y un patrón P constante con valor convertido v,

  • si e tiene tipo entero o tipo de enumeración, o una forma que acepta valores NULL de uno de ellos, y v tiene un tipo entero, el patrón P coincide con el valor e si el resultado de la expresión e == v es true; de lo contrario,
  • si e es de tipo System.Span<char> o System.ReadOnlySpan<char>, y v es una cadena constante, y v no tiene un valor constante de , el patrón se considera coincidente si devuelve ; de lo contrario, v no tiene un valor constante de null; de lo contrario, el patrón se considera coincidente si System.MemoryExtensions.SequenceEqual<char>(e, System.MemoryExtensions.AsSpan(v)) devuelve true; de lo contrario,
  • el patrón Pcoincide con el valor e si object.Equals(e, v) devuelve true.

Ejemplo: la switch instrucción del método siguiente usa cinco patrones constantes en sus etiquetas de mayúsculas y minúsculas.

static decimal GetGroupTicketPrice(int visitorCount)
{
    switch (visitorCount) 
    {
        case 1: return 12.0m;
        case 2: return 20.0m;
        case 3: return 27.0m;
        case 4: return 32.0m;
        case 0: return 0.0m;
        default: throw new ArgumentException(...);
    }
}

ejemplo final

Patrón var 11.2.4

Un var_patterncoincide con cada valor. Es decir, una operación de coincidencia de patrones con una var_pattern siempre se realiza correctamente.

Un var_pattern es aplicable a cada tipo.

var_pattern
    : 'var' designation
    ;
designation
    : simple_designation
    | tuple_designation
    ;
tuple_designation
    : '(' designations? ')'
    ;
designations
    : designation (',' designation)*
    ;

Dado un valor de entrada de patrón (§11.1) e, si la designación es discard_designation, denota un descarte (§9.2.9.2) y el valor de e no está enlazado a nada. (Aunque una variable declarada con ese nombre puede estar en el ámbito en ese momento, esa variable con nombre no se ve en este contexto). De lo contrario, si la designación es single_variable_designation, en tiempo de ejecución, el valor de e está enlazado a una variable local recién introducida (§9.2.9) de ese nombre cuyo tipo es el tipo estático de e y el valor de entrada del patrón se asigna a esa variable local.

Se trata de un error si el nombre var se enlazaría a un tipo en el que se usa un var_pattern .

Si la designación es un tuple_designation, el patrón es equivalente a un positional_pattern (§11.2.5) de la (var de formulario, ... ) donde las designacionesson las que se encuentran dentro de la tuple_designation. Por ejemplo, el patrón var (x, (y, z)) es equivalente a (var x, (var y, var z)).

11.2.5 Patrón posicional

Un positional_pattern comprueba que el valor de entrada no nulles , extrae una secuencia de valores de él y coincide con cada valor extraído en un subpatrón correspondiente. Los valores se extraen de una de estas tres maneras: tratando la entrada como una tupla, invocando un Deconstruct método o indizando la entrada a través System.Runtime.CompilerServices.ITuplede .

Nota: El uso de aquí es distinto de la transformación de deconstrucción de nivel de Deconstruct origen definida en §12.7. nota final

positional_pattern
    : type? '(' subpatterns? ')' property_subpattern? simple_designation?
    ;
subpatterns
    : subpattern (',' subpattern)*
    ;
subpattern
    : pattern
    | subpattern_name ':' pattern
    ;
subpattern_name
    : identifier
    | subpattern_name '.' identifier
    ;

Deje que n sea el número de subpatronesque aparecen entre los paréntesis. La estrategia de coincidencia se selecciona en tiempo de compilación aplicando los siguientes casos en orden; el primer caso cuyas condiciones se cumplen se utilizan y no se tienen en cuenta los casos restantes. Una vez seleccionado un caso, se confirma esa estrategia: cualquier error en tiempo de compilación indicado en ese caso y la coincidencia no pasa a un caso posterior.

  1. Formulario de tupla. Si se omite el tipo y el tipo estático del valor de entrada es un tipo de tupla (§8.3.11) o si el valor de entrada es un literal de tupla (§12.8.6), este caso se aplica. Es un error en tiempo de compilación si n no es igual a la aridad de ese tipo de tupla. En tiempo de ejecución, cada elemento de tupla coincide con el subpatrón correspondiente; la coincidencia se realiza correctamente si todas estas se realizan correctamente. Si algún subpatrón tiene un identificador, ese identificador denominará el elemento de tupla en la posición correspondiente del tipo de tupla.
  2. Forma deconstrucción. De lo contrario, si hay algún tipo presente o se omite el tipo estático y el tipo estático del valor de entrada contiene un método accesible Deconstruct (§12.7), este caso se aplica. Let D ser type if type is present; De lo contrario, deje que D sea el tipo estático del valor de entrada. Se selecciona un Deconstruct método de D con las mismas reglas de resolución de sobrecarga que para una declaración de deconstrucción, con el requisito adicional de que su número de out parámetros es igual a n; es un error en tiempo de compilación si no existe este método. Si el tipo está presente, se trata de un error en tiempo de compilación si el tipo estático del valor de entrada no es compatible con el patrón (§11.2.2) con el tipo ; en tiempo de ejecución, el valor de entrada se prueba con el tipo y, si se produce un error en esa prueba, se produce un error en la coincidencia del patrón posicional. De lo contrario, el valor de entrada se convierte en D y el método seleccionado Deconstruct se invoca con variables nuevas que reciben sus out parámetros. Cada valor recibido coincide con el subpatrón correspondiente y la coincidencia se realiza correctamente si todas estas se realizan correctamente. Si algún subpatrón tiene un identificador, ese identificador denominará el parámetro en la posición correspondiente de Deconstruct.
  3. Formulario ITuple. De lo contrario, si se omite el tipo , ningún subpatrón tiene un identificador y el tipo estático del valor de entrada es object, System.Runtime.CompilerServices.ITupleo un tipo que tiene una conversión de referencia implícita a System.Runtime.CompilerServices.ITuple, este caso se aplica. En tiempo de ejecución, el valor de entrada se prueba por ser una instancia que nonull es de System.Runtime.CompilerServices.ITuple; si se produce un error en esa prueba, se produce un error en la coincidencia de patrones posicionales. De lo contrario, se lee la propiedad del Length valor y, si no es igual a n, se produce un error en la coincidencia del patrón posicional. De lo contrario, para cada i de 1 a n, el valor obtenido al indexar el valor de entrada con i − 1 coincide con el subpatróni-th y la coincidencia se realiza correctamente si todas estas se realizan correctamente.
  4. De lo contrario, no se aplica ningún caso y el positional_pattern es un error en tiempo de compilación.

El orden en el que se coinciden los subpatrones en tiempo de ejecución no está especificado y es posible que una coincidencia con errores no intente coincidir con todos los subpatrones.

Ejemplo: aquí, deconstruimos un resultado de expresión y comparamos los valores resultantes con los patrones anidados correspondientes:

static string Classify(Point point) => point switch
{
    (0, 0) => "Origin",
    (1, 0) => "positive X basis end",
    (0, 1) => "positive Y basis end",
    _ => "Just a point",
};

public readonly struct Point
{
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) => (X, Y) = (x, y);
    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

ejemplo final

Ejemplo: los nombres de los elementos de tupla y los parámetros Deconstruct se pueden usar en un patrón posicional, como se indica a continuación:

var numbers = new List<int> { 10, 20, 30 };
if (SumAndCount(numbers) is (Sum: var sum, Count: var count))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}");
}

static (double Sum, int Count) SumAndCount(IEnumerable<int> numbers)
{
    int sum = 0;
    int count = 0;
    foreach (int number in numbers)
    {
        sum += number;
        count++;
    }
    return (sum, count);
}

La salida generada es

Sum of [10 20 30] is 60

ejemplo final

11.2.6 Patrón de propiedad

Un property_pattern comprueba que el valor de entrada no nulles y coincide recursivamente con los valores extraídos por el uso de propiedades o campos accesibles.

property_pattern
    : type? property_subpattern simple_designation?
    ;
property_subpattern
    : '{' '}'
    | '{' subpatterns ','? '}'
    ;

Se trata de un error si algún subpatrón de un property_pattern no contiene un subpattern_name.

Es un error en tiempo de compilación si el tipo es un tipo de valor que acepta valores NULL (§8.3.12) o un tipo de referencia que acepta valores NULL (§8.9.3).

Nota: Un patrón de comprobación de valores NULL sale de un patrón de propiedad trivial. Para comprobar si la cadena s no es NULL, puede escribir cualquiera de los siguientes formularios:

#nullable enable
string s = "abc";
if (s is object o) ...  // o is of type object
if (s is string x1) ... // x1 is of type string
if (s is {} x2) ...     // x2 is of type string
if (s is {}) ...

nota final

Dada una coincidencia de una expresión e con lossubpatrones} de tipo{ de patrón, se trata de un error en tiempo de compilación si la expresión e no es compatible con el patrón (§11.2.2) con el tipo T designado por tipo. Si el tipo está ausente, se supone que el tipo es el tipo estático de e. Cada subpattern_name que aparezca en el lado izquierdo de sus subpatrones designará una propiedad o campo legible accesible de T. Si la simple_designation de la property_pattern está presente, declara una variable de patrón de tipo T.

En tiempo de ejecución, la expresión se prueba en T. Si se produce un error, se produce un error en la coincidencia del patrón de propiedad y el resultado es false. Si se ejecuta correctamente, cada property_subpattern campo o propiedad se lee y su valor coincide con su patrón correspondiente. El resultado de toda la coincidencia es false solo si el resultado de cualquiera de estos es false. No se especifica el orden en el que se coinciden los subpatrones y es posible que una coincidencia con errores no pruebe todos los subpatrones en tiempo de ejecución. Si la coincidencia se realiza correctamente y el simple_designation del property_pattern es un single_variable_designation, a la variable declarada se le asigna el valor coincidente.

El property_pattern se puede usar para establecer coincidencias de patrones con tipos anónimos.

Un subpattern_name puede hacer referencia a un miembro anidado. En tal caso, el receptor de cada búsqueda de nombres es el tipo del miembro anterior T₀, empezando por el tipo de entrada del property_pattern. Si T es un tipo que acepta valores NULL, T₀ es su tipo subyacente; de lo contrario, T₀ es igual a T. Por ejemplo, un patrón del formulario { Prop1.Prop2: pattern } es exactamente equivalente a { Prop1: { Prop2: pattern } }.

Nota: Esto incluirá la comprobación nula cuando T sea un tipo de valor que acepta valores NULL o un tipo de referencia. Esta comprobación nula significa que las propiedades anidadas disponibles serán las propiedades de T₀, no de T. A medida que se permiten rutas de acceso de miembro repetidas, la compilación de coincidencia de patrones puede aprovechar las ventajas de las partes comunes de los patrones. nota final

Ejemplo:

var o = ...;
if (o is string { Length: 5 } s) ...

ejemplo final

Ejemplo: se puede agregar una comprobación de tipos en tiempo de ejecución y una declaración de variable a un patrón de propiedad, como se indica a continuación:

Console.WriteLine(TakeFive("Hello, world!"));  // output: Hello
Console.WriteLine(TakeFive("Hi!"));            // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' }));  // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' }));  // output: abc

static string TakeFive(object input) => input switch
{
    string { Length: >= 5 } s => s.Substring(0, 5),
    string s => s,
    ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
    ICollection<char> symbols => new string(symbols.ToArray()),
    null => throw new ArgumentNullException(nameof(input)),
    _ => throw new ArgumentException("Not supported input type."),
};

La salida generada es

Hello
Hi!
12345
abc

ejemplo final

11.2.7 Patrón de descarte

Cada expresión coincide con el patrón de descarte, lo que da como resultado el valor de la expresión que se va a descartar.

discard_pattern
    : '_'
    ;

Cuando el contexto sintáctico permite un patrón, si el token _ se resolvería como un simple_name (§12.8.4) a una constante accesible o a un tipo, _no se trata como un discard_pattern. En lugar de:

  • Si _ se resuelve en una constante accesible, se _ interpreta como una constant_pattern (§11.2.3) cuya expresión constante es esa constante.
  • Si _ se resuelve en un tipo, en el lado derecho de un is operador, la construcción relational_expressionis _ se interpreta como operador is-type (§12.15.12.12.1) con ese tipo. En cualquier otro contexto sintáctico que admita un patrón, una resolución completa _ de un tipo no es por sí mismo un patrón válido; sin embargo, _ puede aparecer como el tipo de un declaration_pattern (por ejemplo, _ x) o en otras formas de patrón que denominan explícitamente un tipo.

Esta regla conserva la compatibilidad con versiones anteriores con el código que se define _ como un tipo o identificador antes de la introducción del patrón de descarte. Si _ se resuelve en cualquier cosa que no sea una constante o un tipo accesibles (por ejemplo, una variable local, un parámetro, un campo o un método), la regla no se aplica y _ sigue siendo un discard_pattern.

Nota: Esto es análogo a la regla para var en §11.2.4, excepto que para _ una constante o tipo dentro del ámbito hace _ que se interprete como una referencia a esa declaración en lugar de producir un error. nota final

Si, después de aplicar la regla anterior, el token _ sigue siendo un discard_pattern, es un error en tiempo de compilación para que ese discard_pattern aparezca como el patrón completo de un relational_expression del formulario relational_expressionispatrón, o como el patrón completo de un switch_label. Sin embargo, un discard_pattern puede aparecer como subpatrón de un patrón envolvente (por ejemplo, como subpatrón de un positional_pattern o property_pattern).

Nota: En esos casos, para que coincida con cualquier expresión, use un var_pattern con un descarte var _. nota final

Ejemplo:

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));
Console.WriteLine(GetDiscountInPercent(null));
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));

static decimal GetDiscountInPercent(DayOfWeek? dayOfWeek) => dayOfWeek switch
{
    DayOfWeek.Monday => 0.5m,
    DayOfWeek.Tuesday => 12.5m,
    DayOfWeek.Wednesday => 7.5m,
    DayOfWeek.Thursday => 12.5m,
    DayOfWeek.Friday => 5.0m,
    DayOfWeek.Saturday => 2.5m,
    DayOfWeek.Sunday => 2.0m,
    _ => 0.0m,
};

La salida generada es

5.0
0.0
0.0

En este caso, se usa un patrón de descarte para controlar null y cualquier valor entero que no tenga el miembro correspondiente de la DayOfWeek enumeración. Esto garantiza que la switch expresión controla todos los valores de entrada posibles. ejemplo final

Ejemplo: A continuación se muestra cómo una constante en el ámbito denominada _ cambia la interpretación de un _ brazo en una switch expresión. En WithoutUnderscore, el _ brazo es un discard_pattern y coincide con cualquier valor. En WithUnderscore, la constante _ en el ámbito hace que el _ brazo se interprete como un constant_pattern que coincida solo con el valor 0.

static string WithoutUnderscore(int n) => n switch
{
    1 => "one",
    _ => "other",
};

static string WithUnderscore(int n)
{
    const int _ = 0;
    return n switch
    {
        1 => "one",
        _ => "zero",
        var x => "other: " + x,
    };
}

ejemplo final

Patrón de tipo 11.2.8

Se usa un type_pattern para probar que el valor de entrada del patrón (§11.1) tiene un tipo determinado.

type_pattern
    : type
    ;

Un patrón de tipo que asigne un nombre a un tipo T se aplica a cada tipo E para el que E sea compatible con T el patrón (§11.2.2).

El tipo en tiempo de ejecución del valor se prueba con el tipo utilizando las mismas reglas especificadas en el operador is-type (§12.15.12.1). Si la prueba se realiza correctamente, el patrón coincide con ese valor. Es un error en tiempo de compilación si el tipo es un tipo que acepta valores NULL. Este formulario de patrón nunca coincide con un null valor.

11.2.9 Patrón relacional

Un relational_pattern se usa para probar relacionalmente el valor de entrada del patrón (§11.1) con un valor constante.

relational_pattern
    : '<'  relational_expression
    | '<=' relational_expression
    | '>'  relational_expression
    | '>=' relational_expression
    ;

El relational_expression de un relational_pattern es necesario para evaluar un valor constante.

Los patrones relacionales admiten los operadores <relacionales , <=, >y >= en todos los tipos integrados que admiten estos operadores relacionales binarios con ambos operandos que tienen el mismo tipo: sbyte, , byte, shortushort, , int, uint, longulongcharfloatdoubledecimalnintnuinty enumeraciones.

Es un error en tiempo de compilación si constant_expressiones double.NaN, float.NaNo null_literal.

Cuando el valor de entrada tiene un tipo para el que se define un operador relacional binario integrado adecuado, la evaluación de ese operador se toma como el significado del patrón relacional. De lo contrario, el valor de entrada se convierte en el tipo de constant_expression mediante una conversión explícita que acepta valores NULL o unboxing. Es un error en tiempo de compilación si no existe dicha conversión. Se trata de un error en tiempo de compilación si el tipo de entrada es un parámetro de tipo restringido o un tipo heredado, de y el tipo de System.Numerics.INumberBase<T> entrada no tiene definido ningún operador relacional binario integrado adecuado. El patrón se considera que no coincide si se produce un error en la conversión. Si la conversión se realiza correctamente, el resultado de la operación de coincidencia de patrones es el resultado de evaluar la expresión e «op» v donde e es la entrada convertida, «op» es el operador relacional y v es el constant_expression.

Ejemplo:

Console.WriteLine(Classify(13));
Console.WriteLine(Classify(double.NaN));
Console.WriteLine(Classify(2.4));

static string Classify(double measurement) => measurement switch
{
    < -4.0 => "Too low",
    > 10.0 => "Too high",
    double.NaN => "Unknown",
    _ => "Acceptable",
};

La salida generada es

Too high
Unknown
Acceptable

ejemplo final

11.2.10 Patrón lógico

Un logical_pattern se usa para negar el resultado de una coincidencia de patrón o para combinar los resultados de varias coincidencias de patrones mediante combinación (and) o disjunción (or).

logical_pattern
    : disjunctive_pattern
    ;

disjunctive_pattern
    : disjunctive_pattern 'or' conjunctive_pattern
    | conjunctive_pattern
    ;

conjunctive_pattern
    : conjunctive_pattern 'and' negated_pattern
    | negated_pattern
    ;

negated_pattern
    : 'not' negated_pattern
    | primary_pattern
    ;

not, andy or se denominan colectivamente operadores de patrón.

Un negated_pattern coincide si el patrón que se va a negar no coincide y viceversa. Un conjunctive_pattern requiere que ambos patrones coincidan. Un disjunctive_pattern requiere cualquier patrón que coincida. A diferencia de sus homólogos del operador de lenguaje, && y ||, and y orno son operadores de cortocircuito.

Es un error en tiempo de compilación para que una variable de patrón se declare debajo de un not operador de patrón o or .

Nota: Dado que ni ni notor pueden producir una asignación definitiva para una variable de patrón, es un error declarar uno en esas posiciones. nota final

En un conjunctive_pattern, el tipo de entrada del segundo patrón se limita mediante los requisitos de restricción de tipos del primer patrón de and. El tipo restringido de un patrón P se define de la siguiente manera:

  • Si P es un patrón de tipo, el tipo restringido es el tipo del tipo del patrón de tipo.
  • De lo contrario, si P es un patrón de declaración, el tipo restringido es el tipo del tipo del patrón de declaración.
  • De lo contrario, si P es un patrón recursivo que proporciona un tipo explícito, el tipo restringido es ese tipo.
  • De lo contrario, si P coincide con las reglas de ITuple en un positional_pattern (§11.2.5), el tipo restringido es el tipo System.ITuple.
  • De lo contrario, si P es un patrón constante en el que la constante no es la constante null y donde la expresión no tiene ninguna conversión de expresión constante al tipo de entrada, el tipo restringido es el tipo de la constante.
  • De lo contrario, si P es un patrón relacional en el que la expresión constante no tiene ninguna conversión de expresión constante al tipo de entrada, el tipo restringido es el tipo de la constante.
  • De lo contrario, si P es un or patrón, el tipo restringido es el tipo común del tipo restringido de los subpatrones si existe un tipo común. Para ello, el algoritmo de tipo común solo tiene en cuenta las conversiones de identidad, conversión boxing e implícita, y tiene en cuenta todos los subpatrones de una secuencia de or patrones (ignorando los patrones entre paréntesis).
  • De lo contrario, si P es un and patrón, el tipo restringido es el tipo restringido del patrón correcto. Además, el tipo restringido del patrón izquierdo es el tipo de entrada del patrón derecho.
  • De lo contrario, el tipo restringido de es Pel tipo de P entrada.

Nota: Como se indica en la gramática, not tiene prioridad sobre and, que tiene prioridad sobre or. Esto se puede indicar o invalidar explícitamente mediante paréntesis. nota final

Cuando aparece un patrón en el lado derecho de is, la extensión del patrón viene determinada por la gramática; como resultado, los operadores andde patrón , ory not dentro del patrón enlazan más estrechamente que los operadores lógicos &&, ||y ! fuera del patrón.

Ejemplo:

Console.WriteLine(Classify(13));
Console.WriteLine(Classify(-100));
Console.WriteLine(Classify(5.7));

static string Classify(double measurement) => measurement switch
{
    < -40.0 => "Too low",
    >= -40.0 and < 0 => "Low",
    >= 0 and < 10.0 => "Acceptable",
    >= 10.0 and < 20.0 => "High",
    >= 20.0 => "Too high",
    double.NaN => "Unknown",
};

La salida generada es

High
Too low
Acceptable

ejemplo final

Ejemplo:

Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19)));
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9)));
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11)));

static string GetCalendarSeason(DateTime date) => date.Month switch
{
    3 or 4 or 5 => "spring",
    6 or 7 or 8 => "summer",
    9 or 10 or 11 => "autumn",
    12 or 1 or 2 => "winter",
    _ => throw new ArgumentOutOfRangeException(nameof(date),
      $"Date with unexpected month: {date.Month}."),
};

La salida generada es

winter
autumn
spring

ejemplo final

Ejemplo:

object msg = "msg";
object obj = 5;
bool flag = true;

// This is parsed as: (msg is (not int) or string)
result = msg is not int or string;
Console.WriteLine($"msg (\"msg\"): msg is not int or string: {result}");

// This is parsed as: (obj is (int or string)) && flag
bool result = obj is int or string && flag;
Console.WriteLine($"obj (5), flag (true): obj is int or string && flag: {result}");

// This is parsed as: (obj is int) || ((obj is string) && flag)
result = obj is int || obj is string && flag;
Console.WriteLine($"obj (5), flag (true): obj is int || obj is string && flag: {result}");

flag = false;
// This is parsed as: (obj is (int or string)) && flag
result = obj is int or string && flag;
Console.WriteLine($"obj (5), flag (false): obj is int or string && flag: {result}");

// This is parsed as: (obj is int) || ((obj is string) && flag)
result = obj is int || obj is string && flag;
Console.WriteLine($"obj (5), flag (false): obj is int || obj is string && flag: {result}");

La salida generada es

msg ("msg"): msg is not int or string: True
obj (5), flag (true): obj is int or string && flag: True
obj (5), flag (true): obj is int || obj is string && flag: True
obj (5), flag (false): obj is int or string && flag: False
obj (5), flag (false): obj is int || obj is string && flag: True

ejemplo final

11.2.11 Patrón de lista

Un list_pattern coincide con una secuencia de elementos de una lista o una matriz.

list_pattern
    : list_pattern_clause simple_designation?
    ;

list_pattern_clause
    : '[' (pattern (',' pattern)* ','?)? ']'
    ;

Un list_pattern es compatible con cualquier tipo que sea countable (§18.1), así como indizable (§18.1), tiene un indexador accesible que toma como Index argumento o un indexador accesible con un único int parámetro. Si ambos indexadores están presentes, se prefiere el primero. (Consulte §18.4.2 para obtener más información sobre la compatibilidad con índices implícitos).

Un patrón del formulario expr is [1, 2, 3] es equivalente al código siguiente:

expr.Length is 3
&& expr[new Index(0, fromEnd: false)] is 1
&& expr[new Index(1, fromEnd: false)] is 2
&& expr[new Index(2, fromEnd: false)] is 3

Ejemplo:

int[] numbers = { 1, 2, 3 };

Console.WriteLine(numbers is [1, 2, 3]);  // True
Console.WriteLine(numbers is [1, 2, 4]);  // False
Console.WriteLine(numbers is [1, 2, 3, 4]);  // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3 and not 7]);  // True

ejemplo final

El patrón de descarte (§11.2.7) coincide con cualquier elemento único.

Ejemplo:

List<int> numbers = new() { 1, 2, 3 };

if (numbers is [_, var second, _])
{
    Console.WriteLine($"The second element is {second}.");
}

ejemplo final

11.2.12 Patrón de segmento

Un slice_pattern descarta cero o más elementos. Solo se usará directamente en una list_pattern_clause y, a continuación, solo una vez como máximo en esa cláusula.

slice_pattern
    : '..' pattern?
    ;

Un slice_pattern sin un subpatrón es compatible con cualquier tipo compatible con un list_pattern. Un slice_pattern con un subpatrón es compatible con cualquier tipo que sea countable (§18.1), así como segmentable (§18.1), tiene un indexador accesible que toma como Range argumento o un método accesible Slice con dos int parámetros. Si ambos están presentes, se prefiere el primero. (Consulte §18.4.2 para obtener más información sobre la compatibilidad con índices implícitos).

Un slice_pattern actúa como un descarte adecuado; es decir, no se realizarán pruebas para este patrón. En su lugar, solo afecta a otros nodos, es decir, la longitud y el indexador. Por ejemplo, un patrón del formulario expr is [1, .. var s, 3] es equivalente al código siguiente (si es compatible mediante explícita Index y Range compatibilidad):

expr.Length is >= 2
&& expr[new Index(0, fromEnd: false)] is 1
&& expr[new Range(new Index(1, fromEnd: false), new Index(1, fromEnd: true))] is var s
&& expr[new Index(1, fromEnd: true)] is 3

El tipo de entrada de un slice_pattern es el tipo de valor devuelto del método o this[Range] subyacente Slice con dos excepciones: para stringlas matrices string.Substring y , y RuntimeHelpers.GetSubArray, respectivamente, se usarán.

Ejemplo: se puede usar un patrón de segmento para buscar coincidencias con elementos solo al principio o al final de una secuencia de entrada.

Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]);  // True
Console.WriteLine(new[] { 1, 1 } is [_, _, ..]);  // True
Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]);  // False
Console.WriteLine(new[] { 1 } is [1, 2, ..]);  // False

Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]);  // True
Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]);  // False
Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]);  // True

Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]);  // True
Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]);  // True
Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]);  // False

ejemplo final

Ejemplo: un subpatrón se puede anidar dentro de un patrón de segmento:

MatchMessage("aBBA");  // output: Message aBBA matches; inner part is BB.
MatchMessage("apron"); // output: Message apron doesn't match.

void MatchMessage(string message)
{
    var result = message is ['a' or 'A', .. var s, 'a' or 'A']
        ? $"Message {message} matches; inner part is {s}."
        : $"Message {message} doesn't match.";
    Console.WriteLine(result);
}

Validate(new[] { -1, 0, 1 });     // output: not valid
Validate(new[] { -1, 0, 0, 1 });  // output: valid

void Validate(int[] numbers)
{
    var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0]
        ? "valid" : "not valid";
    Console.WriteLine(result);
}

ejemplo final

Subsumpción de patrón 11.3

En una instrucción switch (§13.8.3), se produce un error si el patrón de un caso está subsumado por el conjunto anterior de casos sin protección (§13.8.3). En una expresión switch (§12.12), se produce un error si el patrón de un switch_expression_arm está subsumado por el conjunto anterior de patrones de switch_expression_armsin protección.

Nota: Esto significa que cualquiera de los valores de entrada habría sido coincidente con uno de los casos o brazos anteriores. nota final Las reglas siguientes definen cuándo un conjunto de patrones subsume un patrón determinado:

Un patrón Pcoincidiría con una constante K si alguna de las condiciones siguientes contiene:

  • la especificación del comportamiento en tiempo de ejecución de ese patrón es que P coincide con K.
  • Pes un type_pattern para el tipo T y no K es y null el tipo en tiempo de ejecución de es K o un tipo derivado de TT o de un tipo que implementa T.
  • P es un relational_pattern con el operador «op» y la constante v, y la expresión K «op» v se evaluaría como true.
  • P es un negated_patternnot P₁ y P₁ no coincidiría con K.
  • P es un conjunctive_patternP₁ and P₂ y ambos P₁ coincidirían K y P₂ coincidirían con K.
  • P es un disjunctive_patternP₁ or P₂ y P₁ coincidiría K con o P₂ coincidiría con K.
  • P es un discard_pattern.

Un conjunto de patrones Qsubsumes un patrón P si alguna de las condiciones siguientes contiene:

  • Pes un patrón constante y cualquiera de los patrones del conjunto Q coincidiría con Pel valor convertido de .
  • Pes un patrón var y el conjunto de patrones es exhaustivo (§11.4) para el tipo del valor de entrada del patrón (Q), y el valor de entrada del patrón no es de un tipo que acepta valores NULL o algún patrón de coincidiría con .Qnull
  • P es un patrón de declaración con tipo T y el conjunto de patrones Q es exhaustivo para el tipo T (§11.4).
  • P es un type_pattern para el tipo T y el conjunto de patrones Q es exhaustivo para el tipo T.
  • P es un relational_pattern con el operador «op» y el valor vconstante, y para cada valor del tipo de entrada que satisface la relación «op», valgún patrón del conjunto Q coincidiría con ese valor.
  • Pes un disjunctive_patternP₁ or P₂ y el conjunto de subsumes Q y P₁ subsumes Qde patrones P₂ .
  • P es un conjunctive_patternP₁ and P₂ y al menos una de las siguientes suspensiones: Q subsumes P₁o Q subsumes P₂.
  • P es un negated_patternnot P₁ y Q es exhaustivo para el tipo de entrada considerando solo los valores que no coinciden con P₁.
  • P es un discard_pattern y el conjunto de patrones Q es exhaustivo para el tipo del valor de entrada del patrón y el valor de entrada del patrón no es de un tipo que acepta valores NULL o algún patrón en Q coincidiría nullcon .
  • Algún patrón de es Q un disjunctive_patternQ₁ or Q₂ y reemplazar ese patrón por Q₁ en Q produciría un conjunto que subsumes P, o reemplazarlo por Q₂ produciría un conjunto que subsumes P.
  • Algún patrón de Q es un negated_patternnot Q₁ y P no coincidiría con ningún valor que Q₁ coincida.

Ejemplo: en la siguiente expresión switch, no se sube el brazo aunque los brazos 1, 2 y 3 compartan el mismo patrón:

object x = 10;
bool b = false;
int y = x switch
{
    int i when !b => 0,
    int i when b  => 1,
    int i         => 2,
    _             => 3
};

Los brazos 1 y 2 tienen guardias no constantes, así que no están vigilados; solo el brazo 3 no está vigilado con el patrón int i, que no subsume el brazo final _ porque no coincide con un valor distintoint de , como null. ejemplo final

La comprobación de subsumpción de list_patterns y slice_patternfunciona igual que los patrones posicionales con ITuple (§11.2.5) los subpatrones correspondientes coinciden con la posición más un nodo adicional para la longitud de las pruebas.

Por ejemplo, el código siguiente genera un error porque ambos patrones producen el mismo DAG:

case [_, .., 1]: // expr.Length is >= 2 && expr[^1] is 1
case [.., _, 1]: // expr.Length is >= 2 && expr[^1] is 1

A diferencia de:

case [_, 1, ..]: // expr.Length is >= 2 && expr[1] is 1
case [.., 1, _]: // expr.Length is >= 2 && expr[^2] is 1

Dada una longitud específica, es posible que dos subpatrones hagan referencia al mismo elemento, en cuyo caso, se inserta una prueba para este valor en el DAG de decisión.

  • Por ejemplo, [_, >0, ..] or [.., <=0, _] se convierte en length >= 2 && ([1] > 0 || length == 3 || [^2] <= 0) donde el valor de longitud de 3 implica la otra prueba.
  • Por el contrario, [_, >0, ..] and [.., <=0, _] se convierte en length >= 2 && [1] > 0 && length != 3 && [^2] <= 0 donde el valor de longitud de 3 no permite la otra prueba.

Como resultado, se producirá un error por algo parecido case [.., p]: case [p]: a porque en tiempo de ejecución, el mismo elemento coincide en el segundo caso.

Si un subpatrón de segmento coincide con una lista o un valor de longitud, los subpatrones se tratarán como si fueran un subpatrón directo de la lista contenedora. Por ejemplo, [..[1, 2, 3]] subsumes un patrón del formato [1, 2, 3].

Las suposiciones siguientes se realizan en los miembros que se usan:

  • Se supone que la propiedad que hace que el tipo sea countable siempre devuelva un valor no negativo, si y solo si el tipo es indexable. Por ejemplo, el patrón { Length: -1 } nunca puede coincidir con una matriz.
  • Se supone que el miembro que hace que el tipo segmentable se comporte bien; es decir, el valor devuelto nunca null es y que es un sublice adecuado de la lista contenedora.

El comportamiento de una operación de coincidencia de patrones no está definido si alguno de los supuestos anteriores no contiene. [

11.4 Exhaustiva de patrones

Informalmente, un conjunto de patrones es exhaustivo para un tipo si, para cada valor posible de ese tipo distinto de NULL, se aplica algún patrón del conjunto. Las reglas siguientes definen cuándo un conjunto de patrones es exhaustivo para un tipo:

Un conjunto de patrones Q es exhaustivo para un tipo T si alguna de las condiciones siguientes contiene:

  1. T es un tipo entero o de enumeración, o una versión que acepta valores NULL de uno de ellos, y para cada valor posible del Ttipo subyacente que no acepta valores NULL, algún patrón de Q coincidiría con ese valor; o
  2. Algún patrón de Q es un patrón var; o
  3. Algún patrón de Q es un patrón de declaración para el tipo Dy hay una conversión de identidad, una conversión de referencia implícita o una conversión boxing de T a D; o
  4. Algunos patrones de Q es un type_pattern para el tipo Dy hay una conversión de identidad, una conversión de referencia implícita o una conversión boxing de T a D; o
  5. Algún patrón de Q es un discard_pattern; o
  6. Los patrones de Q incluyen una combinación de relational_patterns y constant_patterncuyos intervalos cubren colectivamente todos los valores posibles del Ttipo subyacente que no acepta valores NULL. Para float los tipos y double , esto incluye System.Double.NaN o System.Single.NaN respectivamente, ya que NaN no coincide con ningún patrón relacional; o
  7. Algunos patrones de Q es un disjunctive_patternP₁ or P₂, y reemplazar ese patrón por y P₁P₂ en Q produce un conjunto que es exhaustivo para T; o
  8. Algunos patrones de Q es una negated_patternnot P₁, y los patrones de Q junto con los valores no coincidentes cubren P₁ todos los valores posibles de T. Un negated_patternnot P₁ es exhaustivo por sí mismo cuando P₁ no coincide con ningún valor posible de T; o
  9. Algunos patrones de Q es un conjunctive_patternP₁ and P₂, y el conjunto que contiene solo P₁ es exhaustivo para T y el conjunto que contiene solo P₂ es exhaustivo para T.

Nota: Cuando un patrón de tipo incluye tipos que aceptan valores NULL, el patrón puede ser exhaustivo para el tipo, pero sigue generando una advertencia porque el patrón de tipo no coincidirá con un null valor. nota final

Nota: Para los tipos de punto flotante, la combinación de patrones < 0 y >= 0no es exhaustiva porque ninguno de los patrones relacionales coincide con NaN. Un conjunto exhaustivo correcto sería < 0, >= 0y double.NaN (o float.NaN). nota final

Ejemplo:

static void M(byte b)
{
    switch (b) {
        case 0: case 1: case 2: ... // handle every specific value of byte
            break;
        // error: the pattern 'byte other' is subsumed by the (exhaustive)
        // previous cases
        case byte other: 
            break;
    }
}

ejemplo final