Comment j'ai passé l'été avec C # 8

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.


  • Premièrement, c'est merveilleux. J'aurais pu décrire mon intention explicite de déclarer un champ ou une propriété nullable, mais maintenant cette fonction est intégrée dans le langage.
  • Deuxièmement, il n'enregistre pas du tout de NullReferenceException. Et je ne parle pas du fameux "colmatage" des avertissements. C'est juste que pendant l'exécution, personne ne génère de vérification d'argument nul pour vous, alors ne vous précipitez pas pour lancer du code comme throw new ArgumentNullException ()
  • Troisièmement, il y a un grave problème avec le DTO. Par exemple, vous annotez une propriété avec l'attribut Obligatoire. Par conséquent, un objet avec une propriété 100% non nulle entrera dans votre contrôleur WebAPI. Cependant, il n'est pas possible d'associer cet attribut et tous les attributs similaires à des vérifications de types de référence nullables. Le fait est que si vous déclarez MyProperty standard {get; set;} une propriété avec un type NotNull, vous recevrez un avertissement: "[CS8618] La propriété non nullable 'MyProperty' n'est pas initialisée. Envisagez de déclarer la propriété comme nullable" . Ce qui est assez juste, car vous ne pouvez pas garantir une sémantique non nulle pendant le processus d'initialisation. Le seul résultat de cette fonctionnalité est l'impossibilité d'utiliser des propriétés non nulles dans n'importe quel DTO. Mais il y a une bonne nouvelle, il existe une solution de contournement simple - il suffit d'initialiser votre champ avec la valeur par défaut:
     public string MyProperty { get; set; } = ""; 
  • Quatrièmement, les attributs qui gèrent des cas complexes, tels que TryGetValue, sont eux-mêmes assez complexes. Par conséquent, il est très probable que les développeurs peu conscients abusent des opérateurs (!), Ce qui permet de niveler les capacités des types de référence annulables. Un espoir pour les analyseurs.
  • Cinquièmement, et surtout, personnellement, cette opportunité m'a déjà sauvé plusieurs fois des erreurs NullReferenceException. Cela se traduit par un gain de temps banal - de nombreuses erreurs sont détectées au stade de la compilation, et non des tests ou du débogage. Cela est particulièrement vrai non seulement dans le processus de développement d'une logique métier complexe, mais également dans le cas d'un travail trivial avec des bibliothèques externes, DTO et d'autres dépendances, contenant éventuellement null.

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é?

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


All Articles