C # 7 finalmente tiene una característica muy esperada llamada Coincidencia de patrones. Si está familiarizado con lenguajes funcionales como F #, entonces esta función en la forma en que existe actualmente puede decepcionarlo un poco. Pero incluso hoy, puede simplificar el código en una variedad de casos. Más detalles debajo del corte!

Cada nueva característica puede ser peligrosa para el desarrollador que crea la aplicación para la cual el rendimiento es crítico. Los nuevos niveles de abstracciones son buenos, pero para usarlos de manera efectiva, debe comprender cómo funcionan realmente. Este artículo analiza la función de coincidencia de patrones y cómo funciona.
Se puede usar una muestra en C # en una expresión is, así como en el bloque de casos de una instrucción switch.
Hay tres tipos de muestras:
- muestra constante;
- tipo de muestra;
- variable de muestra
La coincidencia de patrones en expresiones es
public void IsExpressions(object o) {
Usando la expresión is, puede verificar si el valor es constante, y usando la verificación de tipo puede determinar adicionalmente la variable de muestra.
Al usar la coincidencia de patrones en sus expresiones, debe prestar atención a varios puntos interesantes:
- La variable ingresada por la instrucción if se envía al ámbito externo.
- La variable ingresada por la instrucción if se asigna explícitamente solo cuando el patrón coincide.
- La implementación actual de la coincidencia de patrones en expresiones no es muy eficiente.
Primero, considere los dos primeros casos:
public void ScopeAndDefiniteAssigning(object o) { if (o is string s && s.Length != 0) { Console.WriteLine("o is not empty string"); }
La primera instrucción if introduce la variable s, visible dentro del método completo. Esto es razonable, pero complica la lógica si otras declaraciones if en el mismo bloque intentan reutilizar el mismo nombre. En este caso, asegúrese de usar un nombre diferente para evitar conflictos.
La variable ingresada en la expresión is se asigna explícitamente solo cuando el predicado es verdadero. Esto significa que la variable n en la segunda instrucción if no está asignada en el operando correcto, pero como ya está declarada, podemos usarla como la variable out en el método int. TryParse.
El tercer punto mencionado anteriormente es el más importante. Considere el siguiente ejemplo:
public void BoxTwice(int n) { if (n is 42) Console.WriteLine("n is 42"); }
En la mayoría de los casos, la expresión se convierte en object.Equals (constante, variable) [aunque las características dicen que el operador == debería usarse para tipos simples]:
public void BoxTwice(int n) { if (object.Equals(42, n)) { Console.WriteLine("n is 42"); } }
Este código invoca dos procesos de conversión de paquetes que pueden afectar significativamente el rendimiento si se utilizan en una ruta de aplicación crítica. Anteriormente, o es una expresión nula llamada empaquetado si la variable o era de tipo anulable (consulte
Código subóptimo para e es nulo ), pero existe la esperanza de que esto se arregle (aquí está la
solicitud correspondiente
en github ).
Si la variable n es de tipo objeto, entonces la expresión o es 42 causará un proceso de "conversión de empaquetamiento" (para el literal 42), aunque un código similar basado en la instrucción switch no conduciría a esto.
La variable de muestra en es expresión
Un patrón variable es un tipo especial de tipo de patrón con una gran diferencia: el patrón coincidirá con cualquier valor, incluso nulo.
public void IsVar(object o) { if (o is var x) Console.WriteLine($"x: {x}"); }
La expresión o es objeto será verdadera si o no es nula, pero la expresión o es var x siempre será verdadera. Por lo tanto, el compilador en modo de lanzamiento * excluye completamente las declaraciones if y simplemente deja la llamada al método Console. Desafortunadamente, el compilador no advierte sobre la falta de disponibilidad del código en el siguiente caso: if (! (O es var x)) Console.WriteLine ("Unreachable"). Hay esperanza de que esto también se arregle.
* No está claro por qué el comportamiento difiere solo en el modo de lanzamiento. Parece que la raíz de todos los problemas es la misma: la implementación inicial de la función no es óptima. Sin embargo, a juzgar por este comentario de Neal Gafter, todo cambiará pronto: “El código para la coincidencia con la muestra se reescribirá desde cero (para admitir también muestras recursivas). Creo que la mayoría de las mejoras de las que está hablando se implementarán en el nuevo código y estarán disponibles de forma gratuita. Sin embargo, esto llevará algún tiempo ".La ausencia de un cheque nulo hace que esta situación sea especial y potencialmente peligrosa. Sin embargo, si sabe exactamente cómo funciona esta muestra, puede ser útil. Se puede usar para introducir una variable temporal en la expresión:
public void VarPattern(IEnumerable<string> s) { if (s.FirstOrDefault(o => o != null) is var v && int.TryParse(v, out var n)) { Console.WriteLine(n); } }
Es expresión y declaración de Elvis
Hay otro caso que puede resultar útil. Un tipo de muestra coincide con un valor solo cuando no es nulo. Podemos usar esta lógica de "filtrado" con un operador de distribución nula para que el código sea más legible:
public void WithNullPropagation(IEnumerable<string> s) { if (s?.FirstOrDefault(str => str.Length > 10)?.Length is int length) { Console.WriteLine(length); }
Tenga en cuenta que se puede usar el mismo patrón para los tipos de valor y los tipos de referencia.
Coincidencia de patrones en bloques de cajas
La funcionalidad de la instrucción switch se ha extendido en C # 7 para que los patrones ahora se puedan usar en caso de cláusulas:
public static int Count<T>(this IEnumerable<T> e) { switch (e) { case ICollection<T> c: return c.Count; case IReadOnlyCollection<T> c: return c.Count;
Este ejemplo muestra el primer conjunto de cambios en la instrucción switch.
- Se puede usar una variable de cualquier tipo con la instrucción switch.
- La cláusula case le permite especificar un patrón.
- El orden de las cláusulas del caso es importante. El compilador arrojará un error si la oración anterior corresponde al tipo base, y la siguiente a la derivada.
- Las ofertas personalizadas se verifican implícitamente como nulas **. En el ejemplo anterior, la cláusula del último caso es válida porque coincide solo cuando el argumento no es nulo.
** La última oración del caso muestra otra función agregada en C # 7: muestras de una variable vacía. El nombre especial _ le dice al compilador que la variable no es necesaria. El tipo de muestra en la cláusula case requiere un alias. Pero si no lo necesita, puede usar _.El siguiente fragmento muestra otra característica de la coincidencia de patrones basada en la instrucción switch: la capacidad de usar predicados:
public static void FizzBuzz(object o) { switch (o) { case string s when s.Contains("Fizz") || s.Contains("Buzz"): Console.WriteLine(s); break; case int n when n % 5 == 0 && n % 3 == 0: Console.WriteLine("FizzBuzz"); break; case int n when n % 5 == 0: Console.WriteLine("Fizz"); break; case int n when n % 3 == 0: Console.WriteLine("Buzz"); break; case int n: Console.WriteLine(n); break; } }
Esta es una versión extraña de la tarea
FizzBuzz que procesa un objeto, no solo un número.
Una declaración de cambio puede incluir múltiples cláusulas de caso del mismo tipo. En este caso, el compilador combina todas las comprobaciones de tipo para evitar cálculos innecesarios:
public static void FizzBuzz(object o) {
Pero hay dos cosas a tener en cuenta:
1. El compilador solo combina verificaciones de tipo secuenciales, y si combina cláusulas de caso con diferentes tipos, se generará un código de menor calidad:
switch (o) {
El compilador lo convertirá de la siguiente manera:
if (o es int n && n == 1) devuelve 1;
if (o is string s && s == "") return 2; if (o is int n2 && n2 == 2) return 3; return -1;
2. El compilador hace todo lo posible para evitar problemas típicos de secuencia.
switch (o) { case int n: return 1;
Sin embargo, el compilador no puede determinar que un predicado sea más fuerte que otro, y reemplaza efectivamente las siguientes cláusulas de caso:
switch (o) { case int n when n > 0: return 1;
Breve coincidencia de patrones
- Los siguientes patrones aparecieron en C # 7: un patrón constante, un patrón de tipo, un patrón variable y un patrón variable vacío.
- Las muestras se pueden usar en sus expresiones y en los bloques de mayúsculas y minúsculas.
- La implementación del patrón constante en la expresión es para los tipos de valor está lejos de ser ideal en términos de rendimiento.
- Las muestras de una variable siempre coinciden, hay que tener cuidado con ellas.
- La instrucción switch se puede usar para establecer verificaciones de tipo con predicados adicionales en las cláusulas when.
Evento de Unity en Moscú - Unity Moscow Meetup 2018.1
El jueves 11 de octubre, el Unity Moscow Meetup 2018.1 se llevará a cabo en la Escuela Superior de Economía. Esta es la primera reunión de desarrolladores de Unity en Moscú esta temporada. El tema del primer mitap será AR / VR. Encontrará informes interesantes, comunicación con profesionales de la industria, así como una zona de demostración especial de MSI.
Detalles