Dans une récente version du podcast DotNet & More Blazor, NetCore 3.0 Preview, C # 8 et non seulement nous venons de mentionner un sujet brûlant comme C # 8. L'histoire de l'expérience avec C # 8 n'était pas assez grande pour lui consacrer un problème distinct, il a donc été décidé de partager avec lui les moyens du genre épistolaire.
Dans cet article, je voudrais parler de mon expérience d'utilisation de C # 8 en production pendant 4 mois. Vous trouverez ci-dessous des réponses aux questions suivantes:
- Comment "épeler" dans le nouveau C #
- Quelles fonctionnalités étaient vraiment utiles
- Quel déçu
Une liste complète des fonctionnalités C # 8 se trouve dans la documentation officielle de Microsoft . Dans cet article, je vais omettre les opportunités que je n'ai pas pu essayer pour une raison ou une autre, à savoir:
- Membres en lecture seule
- Membres de l'interface par défaut
- Structures de référence jetables
- Flux asynchrones
- Indices et plages
Je propose de commencer par l'une des possibilités les plus délicieuses, comme il me semblait auparavant.
Changer d'expressions
Dans nos rêves, nous présentons cette fonction de façon plutôt rose:
int Exec(Operation operation, int x, int y) => operation switch { Operation.Summ => x + y, Operation.Diff => x - y, Operation.Mult => x * y, Operation.Div => x / y, _ => throw new NotSupportedException() };
Mais, malheureusement, la réalité fait ses propres ajustements.
Tout d'abord, il n'est pas possible de combiner les conditions:
string TrafficLights(Signal signal) { switch (signal) { case Signal.Red: case Signal.Yellow: return "stop"; case Signal.Green: return "go"; default: throw new NotSupportedException(); } }
En pratique, cela signifie que dans la moitié des cas, l'expression de commutateur devra être transformée en commutateur normal afin d'éviter le copier-coller.
Deuxièmement, la nouvelle syntaxe ne prend pas en charge les instructions, c'est-à-dire code qui ne renvoie pas de valeur. Cela semblerait, eh bien, et ce n'est pas nécessaire, mais j'ai moi-même été surpris quand j'ai réalisé à quelle fréquence le commutateur est utilisé (en conjonction avec la correspondance de modèle) pour une chose telle que l'assertion dans les tests.
Troisièmement, l'expression de commutateur, qui découle du dernier paragraphe, ne prend pas en charge les gestionnaires multilignes. Comment effrayant nous comprenons au moment d'ajouter les journaux:
int ExecFull(Operation operation, int x, int y) { switch (operation) { case Operation.Summ: logger.LogTrace("{x} + {y}", x, y); return x + y; case Operation.Diff: logger.LogTrace("{x} - {y}", x, y); return x - y; case Operation.Mult: logger.LogTrace("{x} * {y}", x, y); return x * y; case Operation.Div: logger.LogTrace("{x} / {y}", x, y); return x / y; default: throw new NotSupportedException(); } }
Je ne veux pas dire que le nouveau commutateur est mauvais. Non, il est bon, mais pas assez bon.
Propriété et modèles de position
Il y a un an, ils me semblaient les principaux candidats au titre de "opportunité qui a changé le développement". Et, comme prévu, pour utiliser toute la puissance des modèles de position et de propriété, vous devez changer votre approche du développement. À savoir, il est nécessaire d'imiter les types de données algébriques.
Il semblerait, quel est le problème: prenez l'interface du marqueur et c'est parti. Malheureusement, cette méthode a un sérieux inconvénient dans un grand projet: personne ne garantit le suivi au moment de la conception de l'expansion de vos types algébriques. Il est donc très probable qu'au fil du temps, les modifications apportées au code entraînent de nombreux «échecs par défaut» dans les endroits les plus inattendus.
Modèles de tuple
Mais le «frère cadet» des nouvelles possibilités de comparaison avec l'échantillon s'est révélé être une vraie réussite. Le fait est que le modèle de tuple ne nécessite aucun changement dans l'architecture familière de notre code, il simplifie simplement certains cas:
Player? Play(Gesture left, Gesture right) { switch (left, right) { case (Gesture.Rock, Gesture.Rock): case (Gesture.Paper, Gesture.Paper): case (Gesture.Scissors, Gesture.Scissors): return null; case (Gesture.Rock, Gesture.Scissors): case (Gesture.Scissors, Gesture.Paper): case (Gesture.Paper, Gesture.Rock): return Player.Left; case (Gesture.Paper, Gesture.Scissors): case (Gesture.Rock, Gesture.Paper): case (Gesture.Scissors, Gesture.Rock): return Player.Right; default: throw new NotSupportedException(); } }
Mais la meilleure partie est que cette fonctionnalité, qui est suffisamment prévisible, fonctionne très bien avec la méthode Deconstruct. Passez simplement une classe avec Deconstruct implémenté pour changer et utiliser les capacités du modèle de tuple.
Utilisation de déclarations
Cela semble une opportunité mineure, mais cela apporte tellement de joie. Dans toutes les promotions, Microsoft évoque un aspect tel que la réduction de l'imbrication. Mais soyons honnêtes, pas tant que ça qui compte. Mais ce qui est vraiment grave, ce sont les effets secondaires de l'exclusion d'un bloc de code:
- Souvent, lors de l'ajout de using, nous devons extraire le code "à l'intérieur" du bloc en utilisant la méthode copier-coller. Maintenant on n'y pense plus
- Les variables déclarées à l'intérieur de using et utilisées après le Dispose de l'objet using sont un vrai casse-tête. Un problème de moins
- Dans les classes nécessitant des appels Dispose fréquents, chaque méthode durerait 2 lignes de plus. Cela semblerait une bagatelle, mais dans l'état de nombreuses petites méthodes, cette bagatelle ne permet pas d'afficher un nombre suffisant de ces mêmes méthodes sur un seul écran
Par conséquent, une chose aussi simple que d'utiliser des déclarations change tellement le sentiment de codage que vous ne voulez tout simplement pas revenir au c # 7.3.
Fonctions locales statiques
Pour être honnête, sans l'aide de l'analyse de code, je ne remarquerais même pas cette possibilité. Néanmoins, elle s'est fermement installée dans mon code: après tout, les fonctions locales statiques conviennent parfaitement au rôle de petites fonctions pures, car elles ne peuvent pas supporter la fermeture des variables de méthode. En conséquence, c'est plus facile pour le cœur, car vous comprenez qu'il y a une erreur potentielle de moins dans votre code.
Types de référence nullables
Et pour le dessert, je voudrais mentionner la caractéristique la plus importante de C # 8. En vérité, l'analyse des types de référence nullables mérite un article séparé. Je veux juste décrire les sensations.
Résumé
Bien sûr, les opportunités présentées n'atteignent pas une véritable révolution, mais il y a de moins en moins d'écart entre C # et F # / Scala. Que ce soit bon ou mauvais, le temps nous le dira.
Au moment de la publication de cet article, C # 8 était peut-être déjà installé dans votre projet, alors je me demande ce que vous pensez de la nouvelle version de notre langage préféré?