
¡Visual Studio 2019 Preview 2 ya está disponible! Y con él, un par de características más de C # 8.0 están listas para que las pruebe. Se trata principalmente de coincidencia de patrones, aunque tocaré algunas otras noticias y cambios al final.
Original en BlogMás patrones en más lugares.
Cuando C # 7.0 introdujo la coincidencia de patrones, dijimos que esperábamos agregar más patrones en más lugares en el futuro. Ese momento ha llegado! Estamos agregando lo que llamamos patrones recursivos , así como una forma de expresión más compacta de instrucciones de switch
llamadas (¡lo has adivinado!) Expresiones de cambio .
Aquí hay un ejemplo simple de patrones de C # 7.0 para comenzar:
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, observemos que muchas declaraciones de switch
realmente no hacen mucho trabajo interesante dentro de los cuerpos de los case
. A menudo, todos solo producen un valor, ya sea asignándolo a una variable o devolviéndolo (como se indicó anteriormente). En todas esas situaciones, la declaración de cambio es francamente bastante torpe. Se siente como la característica del lenguaje de hace 5 décadas, con mucha ceremonia.
Decidimos que era hora de agregar una forma de expresión de switch
. Aquí está, aplicado al ejemplo anterior:
static string Display(object o) { return o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" }; }
Hay varias cosas aquí que cambiaron de las declaraciones de cambio. Vamos a enumerarlos:
- La palabra clave de
switch
es "infijo" entre el valor probado y la lista de casos {...}
. Eso lo hace más compositivo con otras expresiones, y también más fácil de distinguir visualmente de una declaración de cambio. - La palabra clave
case
y :
se han reemplazado con una flecha lambda =>
por brevedad. default
se ha reemplazado con el patrón _
descarte por brevedad.- ¡Los cuerpos son expresiones! El resultado del cuerpo seleccionado se convierte en el resultado de la expresión de cambio.
Dado que una expresión debe tener un valor o lanzar una excepción, una expresión de cambio que llegue al final sin una coincidencia arrojará una excepción. El compilador hace un gran trabajo advirtiéndole cuando este puede ser el caso, pero no lo obligará a finalizar todas las expresiones de cambio con un comodín: ¡puede que lo sepa mejor!
Por supuesto, dado que nuestro método de Display
ahora consiste en una única declaración de retorno, podemos simplificarlo para que tenga cuerpo de expresión:
static string Display(object o) => o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" };
Para ser honesto, no estoy seguro de qué orientación de formato daremos aquí, pero debe quedar claro que esto es mucho más claro y claro, especialmente porque la brevedad generalmente le permite formatear el interruptor de forma "tabular", como se indicó anteriormente. , con patrones y cuerpos en la misma línea, y los =>
s alineados uno debajo del otro.
Por cierto, planeamos permitir una coma final ,
después del último caso, de acuerdo con todas las otras "listas separadas por comas entre llaves" en C #, pero la Vista previa 2 aún no permite eso.
Patrones de propiedad
Hablando de brevedad, los patrones de repente se están convirtiendo en los elementos más pesados de la expresión de cambio anterior. Hagamos algo al respecto.
Tenga en cuenta que la expresión de cambio utiliza el patrón de tipo Point p
(dos veces), así como una cláusula when
para agregar condiciones adicionales para el primer case
.
En C # 8.0 estamos agregando más elementos opcionales al patrón de tipo, lo que permite que el patrón mismo profundice más en el valor que coincide con el patrón. Puede convertirlo en un patrón de propiedad agregando {...}
contenga patrones anidados para aplicar a las propiedades o campos accesibles del valor. 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 comprueban que o
es un Point
. El primer caso aplica el patrón constante 0
recursivamente a las propiedades X
e Y
de p
, verificando si tienen ese valor. Por lo tanto, podemos eliminar la cláusula when
en este y en muchos casos comunes.
El segundo caso aplica el patrón var
a cada uno de X
e Y
Recuerde que el patrón var
en C # 7.0 siempre tiene éxito y simplemente declara una variable nueva para mantener el valor. Por lo tanto, x
e y
pueden contener los valores int de pX
y pY
.
Nunca usamos p
, y de hecho podemos omitirlo aquí:
Point { X: 0, Y: 0 } => "origin", Point { X: var x, Y: var y } => $"({x}, {y})", _ => "unknown"
Una cosa que sigue siendo cierta para todos los patrones de tipo, incluidos los patrones de propiedad, es que requieren que el valor no sea nulo. Eso abre la posibilidad de que el patrón de propiedad "vacío" {}
se use como un patrón compacto "no nulo". Por ejemplo, podríamos reemplazar el caso de reserva con los siguientes dos casos:
{} => o.ToString(), null => "null"
El {}
trata con los objetos no nulos restantes, y null
obtiene los nulos, por lo que el cambio es exhaustivo y el compilador no se quejará de los valores que caen.
Patrones posicionales
El patrón de propiedad no acortó exactamente el segundo caso de Point
, y no parece que valga la pena, pero hay más que se puede hacer.
Tenga en cuenta que la clase Point
tiene un método Deconstruct
, un llamado deconstructor . En C # 7.0, los deconstructores permitieron que se deconstruyera un valor en la asignación, para que pudiera escribir, por ejemplo:
(int x, int y) = GetPoint();
C # 7.0 no integró la deconstrucción con patrones. Eso cambia con los patrones posicionales, que son una forma adicional de extender los patrones de tipo 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 propiedades:
static string Display(object o) => o switch { Point(0, 0) => "origin", Point(var x, var y) => $"({x}, {y})", _ => "unknown" };
Una vez que el objeto se ha emparejado como un Point
, se aplica el deconstructor y los patrones anidados se aplican a los valores resultantes.
Los deconstructores no siempre son apropiados. Solo deben agregarse a tipos donde está realmente claro cuál de los valores es cuál. Para una clase Point
, por ejemplo, es seguro e intuitivo suponer que el primer valor es X
y el segundo es Y
, por lo que la expresión de cambio anterior es intuitiva y fácil de leer.
Patrones de tuplas
Un caso especial muy útil de patrones posicionales es cuando se aplican a tuplas. Si una instrucción switch se aplica directamente a una expresión de tupla, incluso permitimos que se omita el conjunto adicional de paréntesis, como en switch (x, y, z)
lugar de switch ((x, y, z))
.
Los patrones de tupla son excelentes para probar múltiples piezas de entrada al mismo tiempo. Aquí hay una implementación simple de una 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 optar por incluir hasKey
en la tupla encendida en lugar de usar las cláusulas when
; es realmente una 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, espero que puedan 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
Si bien las características del patrón son las principales que se pondrán en línea en VS 2019 Preview 2, hay algunas más pequeñas que espero que también encuentren útiles y divertidas. No entraré en detalles aquí, pero solo te doy una breve descripción de cada uno.
Usando declaraciones
En C #, el using
sentencias siempre causa un nivel de anidamiento, que puede ser muy molesto y perjudica la legibilidad. Para los casos simples en los que solo desea que se limpie un recurso al final de un ámbito, ahora debe usar declaraciones en su lugar. Las declaraciones de uso son simplemente declaraciones de variables locales con una palabra clave de using
en frente, y sus contenidos se disponen al final del bloque de declaración actual. Entonces en lugar de:
static void Main(string[] args) { using (var options = Parse(args)) { if (options["verbose"]) { WriteLine("Logging..."); } ... }
Puedes simplemente escribir
static void Main(string[] args) { using var options = Parse(args); if (options["verbose"]) { WriteLine("Logging..."); } }
Estructuras de referencia desechables
Las estructuras de referencia se introdujeron en C # 7.2, y este no es el lugar para reiterar su utilidad, pero a cambio vienen con algunas limitaciones severas, como no poder implementar interfaces. Las estructuras de referencia ahora pueden ser desechables sin implementar la interfaz IDisposable
, simplemente teniendo un método Dispose
en ellas.
Funciones locales estáticas
Si desea asegurarse de que su función local no incurra en los costos de tiempo de ejecución asociados con las variables de "captura" (referencia) del ámbito de inclusión, puede declararla como static
. Luego, el compilador evitará la referencia de cualquier cosa declarada en las funciones de cierre, ¡excepto otras funciones locales estáticas!
Cambios desde la vista previa 1
Las características principales de la Vista previa 1 eran tipos de referencia anulables y secuencias asíncronas. Ambos han evolucionado un poco en la Vista previa 2, por lo que si ha comenzado a usarlos, es bueno tener en cuenta lo siguiente.
Tipos de referencia anulables
Hemos agregado más opciones para controlar las advertencias anulables tanto en origen (a través de #pragma warning
directivas de #pragma warning
#nullable
y #pragma warning
) como a nivel de proyecto. También cambiamos la <NullableContextOptions>enable</NullableContextOptions>
archivo del proyecto a <NullableContextOptions>enable</NullableContextOptions>
.
Transmisiones asíncronas
¡Cambiamos la forma de la IAsyncEnumerable<T>
que el compilador espera! Esto hace que el compilador no esté sincronizado con la interfaz proporcionada en .NET Core 3.0 Preview 1, lo que puede ocasionarle muchos problemas. Sin embargo, .NET Core 3.0 Preview 2 saldrá pronto, y eso vuelve a sincronizar las interfaces.
Tener en eso!
Como siempre, ¡estamos ansiosos por sus comentarios! Juega con las nuevas características del patrón en particular. ¿Te encuentras con paredes de ladrillo? ¿Hay algo molesto? ¿Cuáles son algunos escenarios interesantes y útiles que encuentres para ellos? Pulse el botón de comentarios y háganos saber!
Feliz piratería
Mads Torgersen, jefe de diseño de C #