C # 7 a enfin une fonctionnalité attendue depuis longtemps appelée Pattern Matching. Si vous êtes familier avec les langages fonctionnels tels que F #, alors cette fonction sous la forme dans laquelle elle existe actuellement peut légèrement vous décevoir. Mais même aujourd'hui, il peut simplifier le code dans une variété de cas. Plus de détails sous la coupe!

Chaque nouvelle fonctionnalité peut être dangereuse pour le développeur créant l'application pour laquelle les performances sont critiques. De nouveaux niveaux d'abstractions sont bons, mais pour les utiliser efficacement, vous devez comprendre comment ils fonctionnent réellement. Cet article décrit la fonction de correspondance de modèles et son fonctionnement.
Un échantillon en C # peut être utilisé dans une expression is, ainsi que dans le bloc case d'une instruction switch.
Il existe trois types d'échantillons:
- constante d'échantillon;
- échantillon type;
- exemple de variable.
Correspondance de modèle dans les expressions is
public void IsExpressions(object o) {
En utilisant l'expression is, vous pouvez vérifier si la valeur est constante et en utilisant la vérification de type, vous pouvez en outre déterminer la variable exemple.
Lorsque vous utilisez la correspondance de motifs dans des expressions, vous devez faire attention à plusieurs points intéressants:
- La variable entrée par l'instruction if est envoyée à la portée externe.
- La variable entrée par l'instruction if n'est explicitement affectée que lorsque le modèle correspond.
- L'implémentation actuelle de la correspondance de motifs dans les expressions n'est pas très efficace.
Considérons d'abord les deux premiers cas:
public void ScopeAndDefiniteAssigning(object o) { if (o is string s && s.Length != 0) { Console.WriteLine("o is not empty string"); }
La première instruction if introduit la variable s, visible à l'intérieur de la méthode entière. Ceci est raisonnable, mais complique la logique si les autres instructions if du même bloc tentent de réutiliser le même nom. Dans ce cas, assurez-vous d'utiliser un nom différent pour éviter les conflits.
La variable entrée dans l'expression is n'est explicitement affectée que lorsque le prédicat est vrai. Cela signifie que la variable n dans la seconde instruction if n'est pas affectée dans l'opérande de droite, mais comme elle est déjà déclarée, nous pouvons l'utiliser comme variable out dans la méthode int.TryParse.
Le troisième point mentionné ci-dessus est le plus important. Prenons l'exemple suivant:
public void BoxTwice(int n) { if (n is 42) Console.WriteLine("n is 42"); }
Dans la plupart des cas, l'expression est convertie en object.Equals (constant, variable) [bien que les caractéristiques disent que l'opérateur == doit être utilisé pour les types simples]:
public void BoxTwice(int n) { if (object.Equals(42, n)) { Console.WriteLine("n is 42"); } }
Ce code appelle deux processus de conversion d'empaquetage qui peuvent affecter considérablement les performances s'ils sont utilisés sur un chemin d'application critique. Auparavant, l'expression o est nulle appelée emballage si la variable o était de type
nullable (voir
Code sous -
optimal pour e est nul ), mais il y a de l'espoir que ce sera corrigé (voici la
requête correspondante
sur github ).
Si la variable n est de type objet, alors l'expression o est 42 provoquera un processus de «conditionnement-conversion» (pour le littéral 42), bien qu'un code similaire basé sur l'instruction switch n'y conduise pas.
Exemple de variable dans is expression
Un modèle variable est un type spécial de type de modèle avec une grande différence: le modèle correspondra à n'importe quelle valeur, même nulle.
public void IsVar(object o) { if (o is var x) Console.WriteLine($"x: {x}"); }
L'expression o est objet sera vraie si o n'est pas nulle, mais l'expression o est var x sera toujours vraie. Par conséquent, le compilateur en mode de publication * exclut complètement les instructions if et laisse simplement l'appel de méthode Console. Malheureusement, le compilateur ne met pas en garde contre l'indisponibilité du code dans le cas suivant: if (! (O est var x)) Console.WriteLine ("Inaccessible"). Il y a de l'espoir que cela sera également corrigé.
* On ne sait pas pourquoi le comportement diffère uniquement en mode de libération. Il semble que la racine de tous les problèmes soit la même: l'implémentation initiale de la fonction n'est pas optimale. Cependant, à en juger par ce commentaire de Neal Gafter, tout va bientôt changer: «Le code de correspondance avec l'échantillon sera réécrit à partir de zéro (pour prendre également en charge les échantillons récursifs). Je pense que la plupart des améliorations dont vous parlez seront implémentées dans le nouveau code et disponibles gratuitement. Cependant, cela prendra un certain temps. »L'absence de contrôle nul rend cette situation particulière et potentiellement dangereuse. Cependant, si vous savez exactement comment fonctionne cet exemple, il peut être utile. Il peut être utilisé pour introduire une variable temporaire dans l'expression:
public void VarPattern(IEnumerable<string> s) { if (s.FirstOrDefault(o => o != null) is var v && int.TryParse(v, out var n)) { Console.WriteLine(n); } }
Est l'expression et la déclaration d'Elvis
Il existe un autre cas qui peut s'avérer utile. Un type d'échantillon ne correspond à une valeur que lorsqu'elle n'est pas nulle. Nous pouvons utiliser cette logique de «filtrage» avec un opérateur distribué nul pour rendre le code plus lisible:
public void WithNullPropagation(IEnumerable<string> s) { if (s?.FirstOrDefault(str => str.Length > 10)?.Length is int length) { Console.WriteLine(length); }
Notez que le même modèle peut être utilisé à la fois pour les types de valeur et les types de référence.
Correspondance de motif dans les blocs de cas
La fonctionnalité de l'instruction switch a été étendue en C # 7 afin que les modèles puissent désormais être utilisés dans les clauses case:
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;
Cet exemple montre le premier ensemble de modifications apportées à l'instruction switch.
- Une variable de tout type peut être utilisée avec l'instruction switch.
- La clause case vous permet de spécifier un modèle.
- L'ordre des clauses de cas est important. Le compilateur générera une erreur si la phrase précédente correspond au type de base et la suivante à la dérivée.
- La valeur null des offres personnalisées est implicitement vérifiée **. Dans l'exemple ci-dessus, la dernière clause case est valide car elle ne correspond que lorsque l'argument n'est pas nul.
** La dernière phrase du cas montre une autre fonction ajoutée en C # 7 - des échantillons d'une variable vide. Le nom spécial _ indique au compilateur que la variable n'est pas nécessaire. L'exemple de type dans la clause case nécessite un alias. Mais si vous n'en avez pas besoin, vous pouvez utiliser _.L'extrait de code suivant montre une autre caractéristique de la correspondance de modèles basée sur l'instruction switch - la possibilité d'utiliser des prédicats:
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; } }
Il s'agit d'une étrange version de la tâche
FizzBuzz qui traite un objet, pas seulement un nombre.
Une instruction switch peut inclure plusieurs clauses case du même type. Dans ce cas, le compilateur combine toutes les vérifications de type pour éviter les calculs inutiles:
public static void FizzBuzz(object o) {
Mais il y a deux choses à garder à l'esprit:
1. Le compilateur combine uniquement des vérifications de type séquentielles et si vous mélangez des clauses de casse avec différents types, un code de qualité inférieure sera généré:
switch (o) {
Le compilateur le convertira comme suit:
si (o est int n && n == 1) retourne 1;
if (o is string s && s == "") return 2; if (o is int n2 && n2 == 2) return 3; return -1;
2. Le compilateur fait tout son possible pour éviter les problèmes de séquençage typiques.
switch (o) { case int n: return 1;
Cependant, le compilateur ne peut pas déterminer qu'un prédicat est plus fort qu'un autre et remplace efficacement les clauses de cas suivantes:
switch (o) { case int n when n > 0: return 1;
Slip de correspondance de motifs
- Les modèles suivants sont apparus dans C # 7: un modèle constant, un modèle de type, un modèle variable et un modèle variable vide.
- Les échantillons peuvent être utilisés dans les expressions is et dans les blocs case.
- L'implémentation du motif constant dans l'expression est pour les types de valeur est loin d'être idéale en termes de performances.
- Les échantillons d'une variable correspondent toujours, il faut être prudent avec eux.
- L'instruction switch peut être utilisée pour définir des vérifications de type avec des prédicats supplémentaires dans les clauses when.
Événement Unity à Moscou - Meetup Unity Moscow 2018.1
Le jeudi 11 octobre, le Meetup Unity Moscow 2018.1 se tiendra à la Higher School of Economics. Il s'agit de la première réunion des développeurs Unity à Moscou cette saison. Le thème du premier mitap sera AR / VR. Vous y trouverez des rapports intéressants, des communications avec des professionnels de l'industrie, ainsi qu'une zone de démonstration spéciale de MSI.
Détails