Más funciones con patrones en C # 8.0

Más recientemente, se lanzó Visual Studio 2019 Preview 2. Y con él, un par de características adicionales de C # 8.0 están listas para que las pruebe. Esto es principalmente una comparación con la muestra, aunque al final tocaré algunas otras noticias y cambios.


Este artículo está en inglés.




Gracias por traducir nuestro MSP, Lev Bulanov .

Más patrones en más lugares.


Cuando apareció la coincidencia de patrones en C # 7.0, notamos que se espera un aumento en el número de patrones en más lugares en el futuro. Esta vez ha llegado! Estamos agregando lo que llamamos patrones recursivos, así como una forma más compacta de expresiones de cambio llamadas (lo has adivinado) expresiones de cambio.


Para comenzar, aquí hay un ejemplo simple de patrones de C # 7.0:


class 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); } static string Display(object o) { switch (o) { case Point p when pX == 0 && pY == 0: return "origin"; case Point p: return $"({pX}, {pY})"; default: return "unknown"; } } 

Cambiar expresiones


Primero, tenga en cuenta que muchas expresiones de cambio , de hecho, no hacen mucho trabajo interesante en los cuerpos de los casos . A menudo, todos ellos simplemente crean un valor, ya sea asignándolo a una variable o devolviéndolo (como se indicó anteriormente). En todas estas situaciones, el cambio parece estar fuera de lugar. Esto es similar a una característica del lenguaje de cincuenta años.


Decidimos que era hora de agregar un formulario de declaración de cambio . Se aplica al siguiente ejemplo:


 static string Display(object o) { return o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" }; } 

Hay algunas cosas que han cambiado en comparación con las declaraciones de cambio. Vamos a enumerarlos:


  • La palabra clave de cambio es "infijo" entre el valor probado y la lista de {...} casos. Esto lo hace más compositivo con otras expresiones, y también más fácil de distinguir visualmente de la declaración de cambio.
  • La palabra clave y el símbolo de caso: han sido reemplazados por la flecha lambda => para abreviar.
  • El valor predeterminado para brevedad ha sido reemplazado por el patrón _ reset.
  • Los cuerpos son expresiones. El resultado del cuerpo seleccionado se convierte en el resultado de la declaración de cambio.

Debido a que la expresión debe importar o lanzar una excepción, una expresión de selección que termina sin una coincidencia arrojará una excepción. El compilador le avisará cuando esto pueda suceder, pero no lo obligará a finalizar todas las instrucciones select con la función catch-all.


Dado que nuestro método de visualización ahora consiste en una única declaración de retorno, podemos simplificarlo para la expresión:


  static string Display(object o) => o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" }; 

Independientemente de las recomendaciones de formato que se den, deben ser extremadamente claras y concisas. Brevity le permite formatear el interruptor de forma "tabular", como se describió anteriormente, con patrones y cuerpo en la misma línea, y => alineados uno debajo del otro.


Por cierto, planeamos permitir el uso de una coma después del último caso de acuerdo con todas las demás "listas separadas por comas entre llaves" en C #, pero esto aún no está permitido en la Vista previa 2.


Propiedades del patrón


Hablando de brevedad, los patrones de repente se convierten en los elementos más difíciles de las expresiones de elección. Hagamos algo al respecto.


Tenga en cuenta que la expresión select utiliza un patrón de tipo Punto p (dos veces), así como cuándo agregar condiciones adicionales en el primer caso .


En C # 8.0, agregamos elementos opcionales adicionales al tipo de patrones, lo que permite que el patrón mismo profundice en el valor que se asigna al patrón. Puede convertirlo en un patrón de propiedad agregando {...} que contenga patrones anidados, aplicando valores a las propiedades o campos disponibles. Esto nos permite reescribir la expresión de cambio de la siguiente manera:


 static string Display(object o) => o switch { Point { X: 0, Y: 0 } p => "origin", Point { X: var x, Y: var y } p => $"({x}, {y})", _ => "unknown" }; 

Ambos casos aún verifican que o es Point . En el primer caso, el patrón de la constante 0 se aplica de forma recursiva a las propiedades X e Y de la variable p , verificando si tienen este valor. Por lo tanto, podemos eliminar la condición when en este y otros casos similares.


En el segundo caso, el patrón var se aplica a cada uno de X e Y. Recuerde que el patrón var en C # 7.0 siempre tiene éxito y simplemente declara una nueva variable para contener el valor. Entonces x e y contienen valores int para pX y pY .


Nunca usamos p y en realidad podemos omitirlo aquí:


  Point { X: 0, Y: 0 } => "origin", Point { X: var x, Y: var y } => $"({x}, {y})", _ => "unknown" 

Una cosa sigue siendo la misma para todos los tipos de patrones, incluidos los patrones de propiedad, este es un requisito para que el valor sea distinto de cero. Esto abre la posibilidad de usar el patrón de propiedades "vacías" {} como un patrón compacto "distinto de cero". Por ejemplo podríamos reemplazar la alternativa con los siguientes dos casos:


  {} => o.ToString(), null => "null" 

{} Se ocupa de los objetos restantes distintos de cero, y nulo obtiene ceros, por lo que el interruptor es exhaustivo y el compilador no se quejará de los valores faltantes.


Patrones posicionales


El patrón de propiedad no acorta el segundo caso de Punto. No necesita preocuparse por esto, puede hacer aún más.


Tenga en cuenta que la clase Point tiene un método Deconstruct , el llamado deconstructor. En C # 7.0, los deconstructores le permiten "deconstruir" un valor cuando se le asigna, para que pueda escribir, por ejemplo:


 (int x, int y) = GetPoint(); // split up the Point according to its deconstructor 

C # 7.0 no integró la deconstrucción con patrones. Esto cambia con los patrones posicionales, que son una forma adicional de extender los tipos de patrones en C # 8.0. Si el tipo coincidente es un tipo de tupla o tiene un deconstructor, podemos usar patrones posicionales como una forma compacta de aplicar patrones recursivos sin tener que nombrar las propiedades:


 static string Display(object o) => o switch { Point(0, 0) => "origin", Point(var x, var y) => $"({x}, {y})", _ => "unknown" }; 

Después de hacer coincidir el objeto con Point , se aplica el deconstructor y los patrones anidados se aplican a los valores resultantes.


Los deconstructores no siempre son apropiados. Deben agregarse solo a aquellos tipos donde está realmente claro cuál de los valores es cuál. Por ejemplo, para la clase Point , puede suponer que el primer valor es X y el segundo es Y , por lo que la expresión de cambio anterior es clara y fácil de leer.


Patrones de tuplas


Un caso especial muy útil de patrones posicionales es su aplicación a las tuplas. Si la instrucción switch se aplica directamente a la expresión de tupla, incluso permitimos omitir el conjunto adicional de paréntesis, como en switch (x, y, z) en lugar de switch ((x, y, z)) .


Los patrones de tupla son excelentes para probar múltiples entradas al mismo tiempo. Aquí hay una implementación simple de la máquina de estado:


 static State ChangeState(State current, Transition transition, bool hasKey) => (current, transition) switch { (Opened, Close) => Closed, (Closed, Open) => Opened, (Closed, Lock) when hasKey => Locked, (Locked, Unlock) when hasKey => Closed, _ => throw new InvalidOperationException($"Invalid transition") }; 

Por supuesto, podríamos incluir hasKey en la tupla en lugar de usar las cláusulas when , es cuestión de gustos:


 static State ChangeState(State current, Transition transition, bool hasKey) => (current, transition, hasKey) switch { (Opened, Close, _) => Closed, (Closed, Open, _) => Opened, (Closed, Lock, true) => Locked, (Locked, Unlock, true) => Closed, _ => throw new InvalidOperationException($"Invalid transition") }; 

En general, verá que los patrones recursivos y las expresiones de cambio pueden conducir a una lógica de programa más clara y declarativa.


Otras características de C # 8.0 en la Vista previa 2


A pesar de que en VS 2019 Preview 2 las funciones principales para trabajar con patrones son las más importantes, hay varias más pequeñas que, espero, también encuentren útiles e interesantes. No entraré en detalles, solo daré una breve descripción de cada uno.


Usando anuncios


En el uso de C # , los operadores siempre aumentan el nivel de anidamiento, lo que puede ser muy molesto y de poca legibilidad. En casos simples, cuando solo necesita borrar el recurso al final del alcance, se utilizan declaraciones de uso. Las declaraciones de uso son simplemente declaraciones de variables locales con la palabra clave de uso delante de ellas, y sus contenidos se colocan al final del bloque actual de instrucciones. Por lo tanto, en lugar de:

 static void Main(string[] args) { using (var options = Parse(args)) { if (options["verbose"]) { WriteLine("Logging..."); } ... } // options disposed here } 

Solo puedes escribir


 static void Main(string[] args) { using var options = Parse(args); if (options["verbose"]) { WriteLine("Logging..."); } } // options disposed here 

Estructuras de referencia desechables


Las estructuras de referencia se introdujeron en C # 7.2, y parece que no hay lugar para repetirlas aquí. Pero aún así, vale la pena señalar algo: tienen algunas limitaciones, como la imposibilidad de implementar interfaces. Las estructuras de referencia ahora se pueden usar sin implementar la interfaz IDisposable , simplemente usando el método Dispose en ellas.


Funciones locales estáticas


Si desea asegurarse de que su función local no incurra en costos de tiempo de ejecución asociados con la “captura” (referencia) de variables del ámbito, puede declararla como estática. Luego, el compilador evitará el enlace a todo lo que se declara en las funciones adjuntas, ¡excepto otras funciones locales estáticas!


Cambios desde la vista previa 1


Las funciones principales de Preview 1 eran tipos de referencia anulables y flujos asíncronos. Ambas funciones han cambiado un poco en la Vista previa 2, por lo que si comenzó a usarlas, es útil saber lo siguiente.


Tipos de referencia anulables


Hemos agregado más opciones para administrar advertencias anulables tanto en la fuente (a través de las directivas de advertencia #nullable y #pragma ) como a nivel de proyecto. También cambiamos la suscripción al archivo del proyecto a <NullableContextOptions> enable </ NullableContextOptions> .


Hilos asincrónicos


Cambiamos la forma de la interfaz IAsyncEnumerable <T> que el compilador espera. Esto lleva al hecho de que el compilador no se sincroniza con la interfaz proporcionada en .NET Core 3.0 Preview 1, lo que puede causar algunos problemas. Sin embargo, .NET Core 3.0 Preview 2 se lanzará pronto y esto devolverá la sincronización.

Source: https://habr.com/ru/post/440530/


All Articles