Faites-en plus avec les modèles en C # 8.0

Visual Studio 2019 Preview 2 est sorti! Et avec lui, quelques fonctionnalités C # 8.0 supplémentaires sont prêtes à être essayées. Il s'agit principalement de correspondance de motifs, bien que j'aborde quelques autres nouvelles et modifications à la fin.


Original dans le blog

Plus de modèles dans plus d'endroits


Lorsque C # 7.0 a introduit la correspondance de modèles, nous avons dit que nous nous attendions à ajouter plus de modèles à plus d' endroits dans le futur. Ce moment est venu! Nous ajoutons ce que nous appelons des modèles récursifs , ainsi qu'une forme d'expression plus compacte d'instructions switch appelées (vous l'avez deviné!) Expressions switch.


Voici un exemple simple de modèles C # 7.0 pour commencer:


 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"; } } 

Changer d'expressions


Tout d'abord, observons que de nombreuses instructions switch ne font pas vraiment beaucoup de travail intéressant au sein des corps de case . Souvent, ils produisent tous une valeur, soit en l'affectant à une variable, soit en la renvoyant (comme ci-dessus). Dans toutes ces situations, la déclaration switch est franchement plutôt maladroite. Cela ressemble à la fonction linguistique vieille de 5 décennies, avec beaucoup de cérémonie.


Nous avons décidé qu'il était temps d'ajouter une forme d'expression de switch . Le voici, appliqué à l'exemple ci-dessus:


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

Il y a plusieurs choses ici qui ont changé par rapport aux instructions switch. Énumérons-les:


  • Le mot-clé switch est "infixe" entre la valeur testée et la liste {...} de cas. Cela le rend plus compositionnel avec d'autres expressions, et aussi plus facile à distinguer visuellement d'une instruction switch.
  • Le mot clé case et le : ont été remplacés par une flèche lambda => par souci de concision.
  • default a été remplacée par le modèle _ discard pour plus de concision.
  • Les corps sont des expressions! Le résultat du corps sélectionné devient le résultat de l'expression de commutation.

Puisqu'une expression doit avoir une valeur ou lever une exception, une expression de commutateur qui atteint la fin sans correspondance lèvera une exception. Le compilateur fait un excellent travail pour vous avertir lorsque cela peut être le cas, mais ne vous forcera pas à terminer toutes les expressions de commutateur avec un fourre-tout: vous le savez peut-être mieux!


Bien sûr, puisque notre méthode Display se compose désormais d'une seule instruction return, nous pouvons la simplifier pour qu'elle soit dotée d'un corps d'expression:


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

Pour être honnête, je ne sais pas quelle orientation de mise en forme nous donnerons ici, mais il doit être clair que c'est beaucoup plus clair et clair, surtout parce que la brièveté vous permet généralement de formater le commutateur de manière "tabulaire", comme ci-dessus , avec des motifs et des corps sur la même ligne, et les => alignés les uns sous les autres.


Soit dit en passant, nous prévoyons d'autoriser une virgule de fin , après le dernier cas, conformément à toutes les autres "listes séparées par des virgules entre accolades" en C #, mais l'aperçu 2 ne le permet pas encore.


Modèles de propriété


En parlant de brièveté, les motifs deviennent soudainement les éléments les plus lourds de l'expression de commutateur ci-dessus! Faisons quelque chose à ce sujet.


Notez que l'expression de commutateur utilise le modèle de type Point p (deux fois), ainsi qu'une clause when pour ajouter des conditions supplémentaires pour le premier case .


Dans C # 8.0, nous ajoutons plus d'éléments facultatifs au modèle de type, ce qui permet au modèle lui-même de creuser davantage dans la valeur qui correspond au modèle. Vous pouvez en faire un modèle de propriété en ajoutant des modèles imbriqués contenant des {...} à appliquer aux propriétés ou champs accessibles de la valeur. Cela nous permet de réécrire l'expression de commutateur comme suit:


 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" }; 

Les deux cas vérifient toujours que o est un Point . Le premier cas applique ensuite le motif constant 0 manière récursive aux propriétés X et Y de p , vérifiant si elles ont cette valeur. Ainsi, nous pouvons éliminer la clause when dans ce cas et dans de nombreux cas courants.


Le deuxième cas applique le modèle var à chacun de X et Y Rappelez-vous que le modèle var en C # 7.0 réussit toujours et déclare simplement une nouvelle variable pour contenir la valeur. Ainsi, x et y contiennent les valeurs int de pX et pY .


Nous n'utilisons jamais p , et pouvons en fait l'omettre ici:


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

Une chose qui reste vraie pour tous les modèles de type, y compris les modèles de propriété, est qu'ils nécessitent que la valeur soit non nulle. Cela ouvre la possibilité que le modèle de propriété "vide" {} soit utilisé comme modèle compact "non nul". Par exemple, nous pourrions remplacer le cas de secours par les deux cas suivants:


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

Le {} traite des objets null nuls restants, et null obtient les nulls, donc le commutateur est exhaustif et le compilateur ne se plaindra pas de la chute des valeurs.


Modèles de position


Le modèle de propriété n'a pas exactement raccourci le deuxième cas de Point et ne semble pas en valoir la peine, mais il y a plus à faire.


Notez que la classe Point a une méthode Deconstruct , un soi-disant déconstructeur . En C # 7.0, les déconstructeurs permettaient de déconstruire une valeur lors de l'affectation, afin que vous puissiez écrire par exemple:


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

C # 7.0 n'a pas intégré la déconstruction aux motifs. Cela change avec les modèles de position qui sont un moyen supplémentaire d'étendre les modèles de type en C # 8.0. Si le type correspondant est un type de tuple ou a un déconstructeur, nous pouvons utiliser des modèles de position comme un moyen compact d'appliquer des modèles récursifs sans avoir à nommer les propriétés:


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

Une fois que l'objet a été mis en correspondance en tant que Point , le déconstructeur est appliqué et les motifs imbriqués sont appliqués aux valeurs résultantes.


Les déconstructeurs ne sont pas toujours appropriés. Ils ne doivent être ajoutés qu'aux types où il est vraiment clair laquelle des valeurs est laquelle. Pour une classe Point , par exemple, il est sûr et intuitif de supposer que la première valeur est X et la seconde est Y , donc l'expression de commutateur ci-dessus est intuitive et facile à lire.


Modèles de tuple


Un cas spécial très utile des modèles de position est lorsqu'ils sont appliqués à des tuples. Si une instruction switch est appliquée directement à une expression de tuple, nous autorisons même la suppression du jeu de parenthèses supplémentaire, comme dans switch (x, y, z) au lieu de switch ((x, y, z)) .


Les modèles de tuple sont parfaits pour tester plusieurs éléments d'entrée en même temps. Voici une implémentation simple d'une machine à états:


 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") }; 

Bien sûr, nous pourrions choisir d'inclure hasKey dans le tuple activé au lieu d'utiliser des clauses when - c'est vraiment une question de goût:


 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") }; 

Dans l'ensemble, j'espère que vous pouvez voir que les modèles récursifs et les expressions de commutateur peuvent conduire à une logique de programme plus claire et plus déclarative.


Autres fonctionnalités C # 8.0 dans l'aperçu 2


Bien que les fonctionnalités de modèle soient les principales à être mises en ligne dans VS 2019 Preview 2, il y en a quelques-unes plus petites que j'espère que vous trouverez également utiles et amusantes. Je n'entrerai pas dans les détails ici, mais je vais juste vous donner une brève description de chacun.


Utilisation de déclarations


En C #, l' using instructions entraîne toujours un niveau d'imbrication, ce qui peut être très ennuyeux et nuire à la lisibilité. Pour les cas simples où vous voulez simplement qu'une ressource soit nettoyée à la fin d'une étendue, vous devez désormais utiliser des déclarations à la place. Les déclarations using sont simplement des déclarations de variables locales avec un mot-clé using devant, et leur contenu est supprimé à la fin du bloc d'instructions en cours. Donc au lieu de:


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

Vous pouvez simplement écrire


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

Structures de référence jetables


Les structures de référence ont été introduites dans C # 7.2, et ce n'est pas le lieu de réitérer leur utilité, mais en retour, elles comportent de sérieuses limitations, telles que l'impossibilité d'implémenter des interfaces. Les structures de référence peuvent désormais être jetées sans implémenter l'interface IDisposable , simplement en ayant une méthode Dispose .


Fonctions locales statiques


Si vous voulez vous assurer que votre fonction locale n'entraîne pas les coûts d'exécution associés à la "capture" (référencement) des variables de la portée englobante, vous pouvez la déclarer static . Ensuite, le compilateur empêchera la référence à tout ce qui est déclaré dans les fonctions englobantes - à l'exception d'autres fonctions locales statiques!


Changements depuis l'aperçu 1


Les principales caractéristiques de l'aperçu 1 étaient des types de référence annulables et des flux asynchrones. Les deux ont un peu évolué dans l'aperçu 2, donc si vous avez commencé à les utiliser, ce qui suit est bon à savoir.


Types de référence nullables


Nous avons ajouté plus d'options pour contrôler les avertissements #nullable à la fois dans la source (via #pragma warning directives d' #pragma warning #nullable et #pragma warning ) et au niveau du projet. Nous avons également modifié l'opt-in du fichier de projet en <NullableContextOptions>enable</NullableContextOptions> .


Flux asynchrones


Nous avons changé la forme de l' IAsyncEnumerable<T> le compilateur! Cela met le compilateur hors de synchronisation avec l'interface fournie dans .NET Core 3.0 Preview 1, ce qui peut vous causer un certain nombre de problèmes. Cependant, .NET Core 3.0 Preview 2 devrait sortir sous peu, ce qui ramène les interfaces à la synchronisation.


Allez-y!


Comme toujours, nous attendons vos commentaires! Veuillez jouer avec les nouvelles fonctionnalités de motif en particulier. Vous rencontrez des murs de briques? Quelque chose de gênant? Quels sont les scénarios sympas et utiles que vous leur trouvez? Appuyez sur le bouton de rétroaction et faites-le nous savoir!


Piratage heureux


Mads Torgersen, responsable de la conception pour C #

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


All Articles