Coincidencia de patrones recursiva
Nota
Este artículo es una especificación de características. La especificación actúa como documento de diseño de la característica. Incluye cambios de especificación propuestos, junto con la información necesaria durante el diseño y el desarrollo de la característica. Estos artículos se publican hasta que se finalizan los cambios de especificación propuestos e se incorporan en la especificación ECMA actual.
Puede haber algunas discrepancias entre la especificación de características y la implementación completada. Esas diferencias se recogen en las notas de la reunión de diseño de lenguaje (LDM) correspondientes.
Puede obtener más información sobre el proceso de adopción de especificaciones de características en el estándar del lenguaje C# en el artículo sobre las especificaciones de .
Resumen
Las extensiones de coincidencia de patrones para C# permiten muchas de las ventajas de los tipos de datos algebraicos y la coincidencia de patrones de lenguajes funcionales, pero de una manera que se integra sin problemas con la sensación del lenguaje subyacente. Los elementos de este enfoque se inspiran en características relacionadas de los lenguajes de programación F# y Scala.
Diseño detallado
Expresión Is
El operador is
se extiende para probar una expresión con un patrón de .
relational_expression
: is_pattern_expression
;
is_pattern_expression
: relational_expression 'is' pattern
;
Esta forma de relational_expression se suma a los formularios existentes en la especificación de C#. Se trata de un error en tiempo de compilación si el relational_expression a la izquierda del token de is
no designa un valor o no tiene un tipo.
Cada identificador del patrón introduce una nueva variable local que es asignada definitivamente después de que el operador is
sea true
(es decir, asignada definitivamente cuando es verdadero).
Nota: Técnicamente hay una ambigüedad entre el tipo en una
is-expression
y constant_pattern, cualquiera de los cuales podría ser una interpretación válida de un identificador calificado. Intentamos enlazarlo como un tipo para la compatibilidad con versiones anteriores del idioma; solo si se produce un error, lo resuelvemos como hacemos una expresión en otros contextos, a la primera cosa encontrada (que debe ser una constante o un tipo). Esta ambigüedad solo está presente en el lado derecho de una expresiónis
.
Patrones
Los patrones se utilizan en el operador is_pattern, en un switch_statement, y en una switch_expression para expresar la forma de los datos contra la que se compararán los datos entrantes, a los que llamamos el valor de entrada. Los patrones pueden ser recursivos para que las partes de los datos se puedan comparar con subprocesos.
pattern
: declaration_pattern
| constant_pattern
| var_pattern
| positional_pattern
| property_pattern
| discard_pattern
;
declaration_pattern
: type simple_designation
;
constant_pattern
: constant_expression
;
var_pattern
: 'var' designation
;
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern
| subpattern ',' subpatterns
;
subpattern
: pattern
| identifier ':' pattern
;
property_subpattern
: '{' '}'
| '{' subpatterns ','? '}'
;
property_pattern
: type? property_subpattern simple_designation?
;
simple_designation
: single_variable_designation
| discard_designation
;
discard_pattern
: '_'
;
Patrón de declaración
declaration_pattern
: type simple_designation
;
El declaration_pattern prueba que una expresión es de un tipo determinado y la convierte en ese tipo si la prueba tiene éxito. Esto puede introducir una variable local del tipo dado nombrado por el identificador dado, si la designación es una designación_de_variable_única. Esa variable local se asigna definitivamente cuando el resultado de la operación de coincidencia de patrones es true
.
La semántica en tiempo de ejecución de esta expresión es que prueba el tipo en tiempo de ejecución del operando relational_expression izquierdo contra el tipo en el patrón. Si es de ese tipo en tiempo de ejecución (o algún subtipo) y no null
, el resultado del is operator
es true
.
Ciertas combinaciones de tipo estático del lado izquierdo y el tipo especificado se consideran incompatibles y generan un error en tiempo de compilación. Se dice que un valor de tipo estático E
es compatibles con patrones con un tipo T
si existe una conversión de identidad, una conversión de referencia implícita, una conversión boxing, una conversión de referencia explícita o una conversión de unboxing de E
a T
, o si uno de esos tipos es un tipo abierto. Se trata de un error en tiempo de compilación si una entrada de tipo E
no es compatible con el patrón con el tipo en un patrón de tipo con el que se coteja.
El patrón de tipo es útil para realizar pruebas de tipos en tiempo de ejecución de tipos de referencia y reemplaza el idiom.
var v = expr as Type;
if (v != null) { // code using v
Con una versión ligeramente más concisa
if (expr is Type v) { // code using v
Es un error si el tipo es un tipo de valor anulable.
El patrón de tipo se puede usar para probar valores de tipos que aceptan valores nulos: un valor de tipo Nullable<T>
(o un T
encapsulado) coincide con un patrón de tipo T2 id
si el valor no es nulo y el tipo de 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 instrucción if
es 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.
Patrón constante
constant_pattern
: constant_expression
;
Un patrón constante comprueba el valor de una expresión con un valor constante. La constante puede ser cualquier expresión constante, como un literal, el nombre de una variable declarada const
o una constante de enumeración. Cuando el valor de entrada no es un tipo abierto, la expresión constante se convierte implícitamente en el tipo de la expresión coincidente; si el tipo del valor de entrada no es compatibles con el patrón con el tipo de la expresión constante, la operación de coincidencia de patrones es un error.
El patrón c se considera que coincide con el valor de entrada convertido e si object.Equals(c, e)
devolvería true
.
Esperamos ver e is null
como la manera más común de probar para null
en código recién escrito, ya que no puede invocar un operator==
definido por el usuario.
Patrón Var
var_pattern
: 'var' designation
;
designation
: simple_designation
| tuple_designation
;
simple_designation
: single_variable_designation
| discard_designation
;
single_variable_designation
: identifier
;
discard_designation
: _
;
tuple_designation
: '(' designations? ')'
;
designations
: designation
| designations ',' designation
;
Si la designación es una expresión simple_designation, una expresión e coincide con el patrón. Es decir, una coincidencia con un patrón var siempre se realiza correctamente con una simple_designation. Si la simple_designation es una single_variable_designation, el valor de e se enlaza a una variable local recién introducida. El tipo de la variable local es el tipo estático de e.
Si la designación es una tuple_designation, el patrón es equivalente a un positional_pattern con el formato (var
designación, ... )
donde las designaciones son 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))
.
Se trata de un error si el nombre var
se enlaza a un tipo.
Descartar patrón
discard_pattern
: '_'
;
Una expresión e coincide con el patrón _
siempre. En otras palabras, cada expresión coincide con el patrón de descarte.
Un patrón de descarte no se puede usar como el patrón de una is_pattern_expression.
Patrón posicional
Un patrón posicional comprueba que el valor de entrada no es null
, invoca un método Deconstruct
adecuado y realiza una coincidencia de patrones adicional en los valores resultantes. También admite una sintaxis de patrón similar a la tupla (sin el tipo proporcionado) cuando el tipo del valor de entrada es el mismo que el tipo que contiene Deconstruct
, o si el tipo del valor de entrada es un tipo de tupla, o si el tipo del valor de entrada es object
o ITuple
y el tipo en tiempo de ejecución de la expresión implementa ITuple
.
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern
| subpattern ',' subpatterns
;
subpattern
: pattern
| identifier ':' pattern
;
Si se omite el tipo , se toma que sea el tipo estático del valor de entrada.
Dada una coincidencia de un valor de entrada con el patrón tipo(
subpattern_list)
, se selecciona un método mediante la búsqueda en tipo para obtener declaraciones de Deconstruct
accesibles y seleccionar una entre ellas con las mismas reglas que para la declaración de deconstrucción.
Es un error si un positional_pattern omite el tipo, tiene un solo subpattern sin un identifier, no tiene property_subpattern y no tiene simple_designation. Esto desambigua entre un constant_pattern entre paréntesis y un positional_pattern.
Para extraer los valores que deben coincidir con los patrones de la lista,
- Si el tipo se omitió y el tipo del valor de entrada es un tipo de tupla, se requiere que el número de subpatrones sea el mismo que la cardinalidad de la tupla. Cada elemento de la tupla se compara con el subpatrón correspondiente subpattern y la coincidencia es correcta si todas son correctas. Si algún subpatrón tiene un identificador, debe nombrar un elemento de tupla en la posición correspondiente del tipo de tupla.
- De lo contrario, si existe un
Deconstruct
adecuado como miembro de tipo, es un error en tiempo de compilación si el tipo del valor de entrada no es compatible con patrones con el tipo. En tiempo de ejecución, el valor de entrada se prueba contra el tipo. Si falla esto, entonces también fallará la coincidencia de patrones posicionales. Si se ejecuta correctamente, el valor de entrada se convierte en este tipo yDeconstruct
se invoca con variables generadas por el compilador nuevas para recibir los parámetros deout
. Cada valor recibido se compara con el subpatrón correspondiente, y la coincidencia es correcta si todas ellas lo son. Si cualquier subpatrón llamado tiene un identificador , entonces debe nombrar un parámetro en la posición correspondiente deDeconstruct
. - De lo contrario, si se omitió el tipo y el valor de entrada es de tipo
object
oITuple
o de algún tipo que se pueda convertir aITuple
mediante una conversión de referencia implícita, y no aparece ningún identificador entre los subpatrones, hacemos coincidir utilizandoITuple
. - De lo contrario, el patrón es un error en tiempo de compilación.
El orden en el que se emparejan los subpatrones en tiempo de ejecución no está especificado, y es posible que una coincidencia fallida no intente emparejar todos los subpatrones.
Ejemplo
En este ejemplo se usan muchas de las características descritas en esta especificación.
var newState = (GetState(), action, hasKey) switch {
(DoorState.Closed, Action.Open, _) => DoorState.Opened,
(DoorState.Opened, Action.Close, _) => DoorState.Closed,
(DoorState.Closed, Action.Lock, true) => DoorState.Locked,
(DoorState.Locked, Action.Unlock, true) => DoorState.Closed,
(var state, _, _) => state };
Patrón de propiedades
Un patrón de propiedad comprueba que el valor de entrada no es null
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 subpattern de un property_pattern no contiene un identificador (debe tener el segundo formato, que tiene un identificador). Una coma final después del último subpattern es opcional.
Tenga en cuenta que 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.
if (s is object o) ... // o is of type object
if (s is string x) ... // x is of type string
if (s is {} x) ... // x is of type string
if (s is {}) ...
Dada una coincidencia de una expresión e al patrón type{
property_pattern_list}
, es un error en tiempo de compilación si la expresión e no es compatible con patrones con el tipo T designado por el tipo. Si el tipo está ausente, se toma que sea el tipo estático de e. Si el identificador de está presente, declara una variable de patrón de tipo tipo. Cada uno de los identificadores que aparecen en el lado izquierdo de su property_pattern_list debe designar una propiedad o campo legible accesible de T. Si la simple_designation de la property_pattern está presente, define una variable de patrón de tipo T.
En tiempo de ejecución, la expresión se evalúa frente a T. Si esto falla, entonces hay un fallo 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 todo el partido 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 coincida con todos los subpatrones en tiempo de ejecución. Si la coincidencia es correcta y la simple_designation de property_pattern es una single_variable_designation, define una variable de tipo T a la que se le asigna el valor coincidente.
Nota: El patrón de propiedad se puede usar para establecer coincidencias de patrones con tipos anónimos.
Ejemplo
if (o is string { Length: 5 } s)
Cambiar expresión
Se agrega un switch_expression para admitir semántica similar a switch
en un contexto de expresión.
La sintaxis del lenguaje C# se amplía con las siguientes producciones sintácticas.
multiplicative_expression
: switch_expression
| multiplicative_expression '*' switch_expression
| multiplicative_expression '/' switch_expression
| multiplicative_expression '%' switch_expression
;
switch_expression
: range_expression 'switch' '{' '}'
| range_expression 'switch' '{' switch_expression_arms ','? '}'
;
switch_expression_arms
: switch_expression_arm
| switch_expression_arms ',' switch_expression_arm
;
switch_expression_arm
: pattern case_guard? '=>' expression
;
case_guard
: 'when' null_coalescing_expression
;
No se permite la switch_expression como expression_statement.
Estamos buscando relajar esto en una revisión futura.
El tipo de switch_expression es el mejor tipo común (§12.6.3.15) de las expresiones que aparecen a la derecha de los tokens de =>
de la switch_expression_arms si existe dicho tipo y la expresión de cada brazo de la expresión switch se puede convertir implícitamente en ese tipo. Además, agregamos una nueva conversión de expresión de conmutador, que consiste en una conversión implícita predefinida de una expresión de conmutador a cada tipo T
para el cual existe una conversión implícita de la expresión de cada rama a T
.
Es un error si algún patrón switch_expression_arms no puede afectar al resultado porque algún patrón anterior y la protección siempre coincidirán.
Se dice que una expresión switch es exhaustiva si algún brazo de la expresión switch controla cada valor de su entrada. El compilador generará una advertencia si una expresión switch no es exhaustiva.
En tiempo de ejecución, el resultado de la switch_expression es el valor de la expresión del primer switch_expression_arm para el que la expresión del lado izquierdo de la switch_expression coincide con el patrón de switch_expression_arms y para el que el case_guard de switch_expression_arm, si está presente, se evalúa como true
. Si no hay tal switch_expression_arm, el switch_expression lanza una instancia de la excepción System.Runtime.CompilerServices.SwitchExpressionException
.
Paréntesis opcionales al activar un literal de tupla
Para activar un literal de tupla con la switch_statement, debe escribir lo que parecen ser paréntesis redundantes.
switch ((a, b))
{
Para permitir
switch (a, b)
{
los paréntesis de la instrucción switch son opcionales cuando la expresión que se está activando es un literal de tupla.
Orden de evaluación en la coincidencia de patrones
Proporcionar al compilador flexibilidad para reordenar las operaciones ejecutadas durante la coincidencia de patrones puede permitir la flexibilidad que se puede usar para mejorar la eficacia de la coincidencia de patrones. El requisito (no aplicado) sería que las propiedades a las que se accede en un patrón y los métodos Deconstruct deben ser "puros" (libres de efectos secundarios, idempotentes, etc.). Esto no significa que agregaríamos pureza como concepto de lenguaje, solo que permitiríamos la flexibilidad del compilador en las operaciones de reordenación.
Resolución 2018-04-04 LDM: confirmado: el compilador puede reordenar llamadas a Deconstruct
, accesos a propiedades e invocaciones de métodos en ITuple
, y puede suponer que los valores devueltos son los mismos de varias llamadas. El compilador no debe invocar funciones que no puedan afectar al resultado y seremos muy cuidadosos antes de realizar cambios en el orden de evaluación generado por el compilador en el futuro.
Algunas optimizaciones posibles
La compilación de la coincidencia de patrones puede aprovechar las partes comunes de los patrones. Por ejemplo, si la prueba de tipo de nivel superior de dos patrones sucesivos en un switch_statement es el mismo tipo, el código generado puede omitir la prueba de tipos para el segundo patrón.
Cuando algunos de los patrones son enteros o cadenas, el compilador puede generar el mismo tipo de código que genera para una instrucción switch en versiones anteriores del lenguaje.
Para obtener más información sobre estos tipos de optimizaciones, consulte [Scott y Ramsey (2000)].
C# feature specifications