2 points par GN⁺ 2024-10-20 | 1 commentaires | Partager sur WhatsApp

-.NET 9.0 réduit fortement le temps d’exécution par rapport à .NET 8 dans plusieurs scénarios LINQ courants, et supprime même les allocations dans certains benchmarks

  • L’une des améliorations clés consiste à obtenir un ReadOnlySpan<T> via TryGetSpan() lors de l’itération sur un tableau ou une List<T>, afin de réduire le coût de parcours
  • TryGetSpan() identifie TSource[] et List<TSource> par comparaison de type, mais la façon dont il récupère un span sur le tableau interne de List<T> est une optimisation de type Unsafe qui peut être invalidée si la capacité change
  • Le LINQ de .NET 9 reconnaît des chaînes d’appels fréquentes pour créer des iterators spécialisés, puis applique des optimisations supplémentaires dans des méthodes terminales comme Count(), First(), Last(), ElementAt() et Sum()
  • Une simple migration et recompilation suffit pour profiter d’une partie des gains de performance de LINQ, avec aussi des optimisations comme l’usage du SIMD et la détection précoce des séquences vides

Pourquoi le parcours des tableaux et des listes est plus rapide

  • Le premier benchmark stocke Enumerable.Range(1, 10_000).ToArray() dans un IEnumerable<int>, puis exécute Count, All, Any, First, Single, Last afin de comparer .NET 8 et .NET 9
  • Il utilise BenchmarkDotNet, et le projet doit cibler net8.0;net9.0 puis être compilé en mode Release
  • Dans .NET 9, le temps d’exécution de plusieurs méthodes chute fortement et les allocations disparaissent aussi
    • LinqCount : 16,198.490 ns à 3,043.563 ns, de 32 B alloués à aucune allocation
    • LinqAny : 17,096.735 ns à 2,483.927 ns, de 32 B alloués à aucune allocation
    • LinqFirst : 15,289.747 ns à 2,243.341 ns, de 32 B alloués à aucune allocation
    • LinqSingle : 21,684.114 ns à 4,884.329 ns, de 32 B alloués à aucune allocation
    • LinqAll : 10.588 ns à 2.562 ns, de 32 B alloués à aucune allocation
    • LinqLast : 15.967 ns à 6.918 ns

Ce que change TryGetSpan()

  • La principale cause du gain de performances est l’utilisation de TryGetSpan()
  • Si l’enumerable parcouru est un tableau ou une liste, TryGetSpan() renvoie un ReadOnlySpan<T> permettant une itération plus rapide
  • Le branchement central vérifie source.GetType() == typeof(TSource[]) ou source.GetType() == typeof(List<TSource>), puis récupère le span
    • Les tableaux sont traités avec Unsafe.As<TSource[]>(source)
    • Les listes utilisent CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source)) pour obtenir un span à partir du tableau interne
  • Dans le code, source.GetType() est appelé deux fois et le traitement ne passe pas par un cast suivi d’un test de null, mais c’est un choix fait par les experts performance de .NET en tenant compte des optimisations du compilateur C# et du JIT
  • Dans une pile .NET hautement optimisée, les micro-optimisations peuvent donner des résultats différents de ce que laisse penser l’apparence du code

Les contraintes de CollectionsMarshal.AsSpan()

  • List<TSource> référence en interne un tableau, et lorsqu’il faut augmenter ou réduire la capacité de la liste, un nouveau tableau est créé puis référencé
  • CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source)) obtient un Span<TSource> à partir de ce tableau interne
  • Si la capacité de la liste change d’une manière ou d’une autre, le tableau obtenu ainsi peut devenir invalide
  • À cause de cette contrainte, certaines opérations Enumerable qui incluent une itération différée, comme avec yield, peuvent difficilement s’appuyer sur cette optimisation
  • Le nom même de System.Runtime.CompilerServices.Unsafe reflète ce risque

Portée des appels à TryGetSpan()

  • NDepend a analysé System.Linq.dll pour identifier les appelants directs et indirects de TryGetSpan()
  • Le chemin de l’assembly analysé est C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.0-rc.1.24431.7\System.Linq.dll
  • Une requête de code a été générée depuis TryGetSpan() pour identifier les appelants, puis 56 méthodes correspondantes ont été exportées dans un graphe de dépendances
  • De nombreuses méthodes Enumerable standard tentent un parcours via span lorsque la collection est un tableau ou une liste
  • Mais comme la technique qui retient le tableau interne d’une liste n’est pas sûre, les opérations nécessitant une exécution différée gardent certaines limitations

Optimisations fondées sur des iterators spécialisés

  • Le deuxième benchmark reprend les cas du PR Consolidate LINQ’s internal IIListProvider/IPartition into base Iterator class
  • Les tests couvrent notamment Distinct().First(), Append().Select().Last(), Reverse().Count(), DefaultIfEmpty().Select().ElementAt(), Skip().Take().ElementAt(), Union().First(), Select().Where().Select().Sum()
  • Dans .NET 9, certaines chaînes d’appels deviennent extrêmement rapides
    • DistinctFirst : 65.318 ns à 11.192 ns, de 328 B alloués à aucune allocation
    • AppendSelectLast : 4,122.007 ns à 2.661 ns, de 144 B alloués à aucune allocation
    • DefaultIfEmptySelectElementAt : 4,090.818 ns à 5.724 ns, de 144 B alloués à aucune allocation
    • RangeUnionFirst : 66.309 ns à 6.193 ns, de 344 B alloués à aucune allocation
    • ListSkipTakeElementAt : 6.268 ns à 2.916 ns
    • RangeReverseCount : 11.024 ns à 6.134 ns
  • À l’inverse, SelectWhereSelectSum est devenu plus lent, passant de 3,959.622 ns dans .NET 8 à 4,460.008 ns dans .NET 9, avec toujours 112 B alloués

Reconnaissance des chaînes LINQ fréquentes

  • L’équipe performance de .NET a conçu le code pour reconnaître des chaînes d’appels LINQ courantes
  • Lorsqu’une chaîne donnée est détectée, un iterator spécialisé est créé pour traiter le flux de travail plus efficacement
  • Si la chaîne se termine par une méthode comme Count(), First(), Last(), ElementAt() ou Sum(), des optimisations supplémentaires deviennent possibles
  • Par exemple, OrderBy(criteria).First() peut être optimisé pour s’exécuter comme Min(criteria)

Structure de Iterator<T> et des classes dérivées

  • En interne, LINQ contient une classe de base abstraite Iterator<T> et 40 classes dérivées
  • Ces classes sont toutes imbriquées dans la classe Enumerable
  • Iterator<T> est une classe abstraite, mais ses méthodes sont virtuelles, donc les classes dérivées ne redéfinissent que celles dont elles ont besoin
  • Cette structure sert de base pour embarquer des comportements spécialisés selon les chaînes d’appels

Exemple de ListWhereSelectIterator<TSource, TResult>

  • ListWhereSelectIterator<TSource, TResult> traite la chaîne Where(...).Select(...) sur une liste avec un seul iterator
  • Cet iterator est créé par la redéfinition de Select() dans ListWhereIterator<TSource, TResult>
  • ListWhereIterator<TSource> est créé lorsque Enumerable.Where() vérifie si la source est une List<TSource>
  • ListWhereSelectIterator<TSource, TResult> ne redéfinit pas de méthodes comme TryGetFirst() ou TryGetLast()
  • Le cœur du gain de performance vient du fait que la chaîne très fréquente Where(...).Select(...) sur une liste est fusionnée en un seul iterator au lieu de deux
    • Dans MoveNext(), les deux delegates _predicate et _selector sont appelés ensemble

Exemple de IListSkipTakeIterator<TSource>

  • IListSkipTakeIterator<TSource> est un iterator spécialisé créé quand les conditions s’y prêtent
  • MoveNext() utilise _state - 1 comme index 0-based dans la liste
  • Un champ d’index séparé serait plus lisible, mais une valeur biaisée est stockée dans _state afin de réduire la taille des champs de l’iterator
  • L’optimisation de cet iterator consiste à éviter de parcourir inutilement les éléments situés hors de la plage _minIndexInclusive à _maxIndexInclusive

Des optimisations obtenues par la seule migration

  • Dans .NET 9, plusieurs scénarios LINQ courants deviennent plus rapides
  • Pour profiter des améliorations du nouveau runtime, il suffit de migrer puis de recompiler
  • LINQ est aussi optimisé d’autres façons
    • Pour des cas comme la somme de séquences d’entiers, le SIMD est utilisé là où c’est possible
    • Les séquences vides sont détectées plus tôt, ce qui réduit le coût d’énumération
  • DeepDotnet videos peut servir de ressource d’apprentissage .NET avec Scott Hanselman et Stephen Toub

1 commentaires

 
GN⁺ 2024-10-20
Avis sur Hacker News
  • À mon avis, la partie la plus utile de LINQ n’est ni la structure d’extension fondée sur les arbres syntaxiques de IQueryable, ni la syntaxe intégrée au langage, mais les méthodes d’extension de IEnumerable
    Autrefois, on appelait cela, de façon un peu déroutante, « LINQ to Objects », et cela permet d’écrire du C# de manière concise, dans un style fonctionnel
    L’article d’origine traite surtout de l’optimisation de ces méthodes d’extension
    Ce n’est qu’après avoir appris Haskell que cette approche m’a vraiment parlé, et elle partage aussi certains des avantages et pièges de Haskell, comme l’évaluation paresseuse
    Utilisée sans discernement, elle peut produire du code obscur et lent ; je ne la recommanderais donc pas si personne dans l’équipe ne connaît les idiomes fonctionnels de base et l’évaluation paresseuse

    • Moi aussi, je préfère l’aspect fonctionnel des extensions LINQ pour IEnumerable et IQueryable
      C’est plus facile à raisonner et, dans des environnements comme Entity Framework, ce n’est pas toujours le choix le plus rapide, mais c’est généralement une option tout à fait correcte
      J’aime aussi utiliser Dapper plutôt qu’EF
      Cela dit, les projets C# ont tendance à empiler un nombre absurde de couches d’abstraction, et le développement « enterprise » est généralement pénible à regarder
    • Moi aussi, j’utilise LINQ de cette façon
      La nomenclature est parfois un peu non standard, mais tout ce dont on a besoin est là
      Eric Lippert a écrit une excellente série d’articles expliquant les monades en lien avec LINQ : https://ericlippert.com/2013/04/02/monads-part-twelve/
    • J’ai toujours utilisé LINQ uniquement avec la syntaxe par méthodes
      Je n’aime pas avoir un autre langage « intégré » à l’intérieur du langage hôte, et le résultat final doit de toute façon revenir à du C#
      Même lorsqu’on n’utilise pas d’ORM comme Entity Framework ou Dapper, je préfère placer la logique d’accès aux données, SQL compris, dans un projet abstrait séparé
      Ainsi, elle ne se répand pas dans toute l’application et on peut la remplacer si un autre SGBDR devient nécessaire
      En 20 ans, cela ne m’est arrivé qu’une seule fois, mais bon
      Quand des développeurs juniors utilisent LINQ, leur mettre un profiler et un débogueur entre les mains aide à comprendre ce qui se passe en interne
      Il est parfois aussi utile de leur faire écrire d’abord la solution avec une boucle for et de la logique C# ordinaire, puis de la comparer à l’implémentation LINQ, afin de voir les avantages et les inconvénients des deux approches
    • Si vous aimez Haskell, d’autres usages de LINQ pourraient aussi vous plaire, comme la construction de parseurs combinatoires à l’aide de la syntaxe de requête
      La syntaxe de requête n’est pas codée en dur exclusivement pour IEnumerable : c’est simplement son comportement par défaut, et on peut l’utiliser presque partout
      Elle fonctionne un peu comme la surcharge d’opérateurs
      [1] : https://github.com/acple/ParsecSharp/blob/da8d0cb9ec39e28dd9...
    • Si la syntaxe LINQ disparaissait demain, elle ne me manquerait pas beaucoup, mais la composition fonctionnelle, elle, est vraiment puissante et rend la maintenance plus facile
  • Je ne comprends pas pourquoi l’équipe dotnet n’investit pas davantage de ressources et de temps dans l’outillage
    Il faudrait des doctests et de la génération de documentation, la possibilité d’écrire de meilleurs tests unitaires, plus rapides, à côté du code réel, un meilleur accès au code source, un environnement où appuyer sur F12 ne nécessite pas de décompiler une DLL, et un hub centralisé pour les packages et la documentation comme pkg.go.dev ou docs.rs
    La plupart des packages NuGet n’ont soit aucune documentation, soit seulement un README GitHub, soit un court wiki
    D’autres écosystèmes comme Rust, Go, Java ou Python ont des années-lumière d’avance sur ce point

    • On en vient à plaisanter en disant que Microsoft a investi dans OpenAI parce que c’est le seul moyen vaguement sensé de parcourir la documentation des packages .NET/NuGet
      Mais, de façon terrifiante, j’ai aussi l’impression que ce n’est pas si loin de la vérité
    • Désormais, la documentation Microsoft contient des liens directs vers le code source de la méthode que l’on consulte
      Exemple : https://learn.microsoft.com/en-us/dotnet/api/system.string.s...
      Pour le code source des packages NuGet, c’est aussi facile si Source Link est activé, mais la fonctionnalité est encore relativement récente et tous les packages ne l’ont pas encore adoptée
    • Je suis d’accord, mais le fait que le C# open source soit relativement récent y est probablement aussi pour quelque chose
      J’imagine que la plupart du code C# est encore écrit en entreprise en source fermé
      Si Microsoft continue dans la direction plus ouverte qu’on observe ces dernières années, cela devrait s’améliorer avec le temps
      Certaines de ces fonctionnalités sont fournies par des outils comme Resharper, et je me demande s’il n’existe pas un accord explicite ou implicite pour éviter de marcher sur les plates-bandes des uns et des autres
      Honnêtement, la plupart de la documentation que j’ai vue dans des projets C# était de mauvaise qualité, si bien que je finissais par lire le code source
      Mon expérience est que, même avec beaucoup d’outils d’autocomplétion, cela aide surtout à écrire, pas vraiment à lire
    • Sandcastle Help File Builder existe depuis très longtemps et, si je me souviens bien, a commencé comme un projet interne à Microsoft, mais curieusement peu de bibliothèques l’utilisent
      https://github.com/EWSoftware/SHFB
    • Il existe aussi une façon d’écrire les tests à côté du code : https://clipperhouse.com/go-test-csharp/
      Je ne sais pas trop si je la recommanderais
      J’ai essayé puis je suis revenu en arrière ; les tests semblaient prendre plus longtemps, peut-être parce que la mise en cache des artefacts de build se dégradait
  • Plutôt que « amélioration des performances de LINQ », il faudrait dire « amélioration des performances de leur propre implémentation de List »
    Microsoft semble passer son temps à améliorer les parties dont ils ont besoin, plutôt qu’à apporter des améliorations générales
    LINQ, en particulier la syntaxe de requête plutôt que les extensions de méthodes, a besoin d’investissements
    Il faut surtout réduire les allocations de lambdas et, si possible, simplifier les lambdas au moment de la compilation
    Il faudrait des lambdas locales de type valeur, ou une stratégie pour que l’allocation de lambdas n’ait plus le même surcoût qu’aujourd’hui
    Les variables LINQ devraient aussi désormais prendre en charge le joker (_), mais cela a été complètement ignoré lors de son introduction dans les lambdas
    De plus, en dernier élément d’une expression LINQ, on devrait pouvoir utiliser des types relevés comme IEnumerable ou Option au lieu de select ...
    Dans certains cas d’usage, select crée un surcoût inutile et limite aussi des choses comme les expressions LINQ en récursion terminale
    Les bibliothèques comme la mienne, qui misent tout sur LINQ mais n’utilisent pas IEnumerable, IQueryable ni les extensions LINQ, continuent d’être ignorées
    Parce que Microsoft se concentre uniquement sur l’amélioration des performances de ses propres projets
    Un bon exemple est l’inférence de lambdas améliorée
    Elle a été accélérée parce qu’elle était nécessaire aux Minimal APIs d’ASP.NET Core
    Une grande partie des fonctionnalités du langage et du framework semble guidée davantage par les besoins internes de Microsoft que par ceux de la communauté
    Le pire, c’est que l’ensemble des méthodes magiques, comme Select, SelectMany, Where pour les extensions LINQ, mais aussi GetAwaiter, continue de s’étendre
    Au lieu d’ajouter les traits de kinds d’ordre supérieur qui sont réellement nécessaires pour dissiper cette magie, Microsoft ajoute des fonctionnalités pour ses propres besoins, principalement ceux du compilateur
    Résultat, tout reste faiblement typé, et le compilateur ne peut détecter les choses que de façon approximative
    LINQ est l’un des grands différenciateurs du langage, mais il a été quasiment laissé à l’abandon depuis C# 3
    Il est vraiment dommage qu’on voie encore LINQ comme utile seulement pour parcourir des listes, surtout leur propre implémentation de listes
    Les améliorations de performances en elles-mêmes sont appréciables et aideront beaucoup d’utilisateurs, mais le focus reste toujours étroit et limite le potentiel
    [1] https://github.com/louthy/language-ext/

    • Si vous avez des retours utiles, le mieux est d’ouvrir une issue ou d’envoyer une PR sur dotnet/runtime
      Beaucoup des améliorations de performances LINQ abordées dans l’article sont arrivées de cette manière
    • La bibliothèque a l’air très intéressante, mais elle semble aussi avoir été configurée, dès le départ, d’une manière qui la rend facile à ignorer
      Il y a beaucoup d’instructions using, ce qui n’est pas un gros problème pour quelqu’un qui comprend comment découper finement les projets selon les besoins et séparer les préoccupations
      Mais la plupart des développeurs ne structurent pas leurs projets ainsi, et ce genre de petit détail peut devenir un obstacle pour le développeur moyen
      Les développeurs juniors ont déjà souvent du mal avec la syntaxe et les méthodes LINQ standard, en particulier aussi avec les performances
      C’est bien que le README mentionne ce point
      D’habitude, les bibliothèques cherchent surtout à se « vendre », et j’apprécie que celle-ci explique réellement où elle est forte et quel est son objectif
      Le fait qu’elle soit présentée comme non idiomatique peut aussi poser problème à quelqu’un qui apprend C#/.NET
      Microsoft veut probablement que les outils et le langage suivent certaines pratiques, et une nomenclature naturellement alignée sur la programmation fonctionnelle peut être vue comme un obstacle assez important lorsque Microsoft envisage des améliorations
      J’ai mis une étoile au dépôt, et ce que vous avez créé m’intéresse beaucoup
      Dans quelques grosses applications que j’ai faites récemment, j’ai utilisé un type Result qui ressemble grosso modo à Option
      Mais en regardant à nouveau la bibliothèque, je me rends compte que je pensais assez bien connaître la programmation fonctionnelle, alors qu’en réalité ce n’est pas le cas
      Je suis plutôt bon en C# et j’ai fait des choses complexes, mais malgré beaucoup de lectures, la programmation fonctionnelle reste difficile à maîtriser, et F# For Fun And Profit est ce qui m’a paru le plus compréhensible
      Au final, cela ne veut pas dire que vous faites quelque chose de mal
      Microsoft visera probablement les développeurs moyens ou débutants, qui constituent la majorité de son écosystème
      J’espère que cette bibliothèque pourra bénéficier d’améliorations internes
      On voit que vous y avez consacré énormément de temps, et le simple nombre d’étoiles GitHub suffit à indiquer qu’il y a des gens qui l’utilisent vraiment et en tirent profit
      Désolé si cela a pu sonner comme du mépris, mais le travail réalisé est intéressant, et la documentation semble suffisante pour apprendre progressivement des concepts que je ne connais pas
  • Plus C# empruntera à F#, mieux ce sera
    J’attends que les unions discriminées arrivent enfin dans C#, pour pouvoir faire correctement de la modélisation de domaine

    • Il est intéressant de voir ce genre de remarque revenir souvent dans la communauté .NET
      À chaque fois, la question qui me vient est : « pourquoi ne pas simplement utiliser F# ? »
      C# essaie de rattraper son retard depuis des années
      Si F# porte beaucoup d’innovations dans l’écosystème .NET et a plusieurs années d’avance côté fonctionnalités, je me demande pourquoi cet effort n’est pas récompensé par l’usage
      Si vous voulez orienter le développement d’un langage dans une certaine direction, il faut l’encourager par de vrais choix
      Cela a fonctionné ainsi dans l’écosystème Java, et Java s’améliore désormais lui aussi
      Quand le marché grandit, une boucle de rétroaction positive peut se créer, avec davantage d’efforts d’ingénierie
      Après des années à lire les forums, j’ai fortement l’impression que le camp C# veut « simplement » rester dans son camp et attendre
      Cela ressemble un peu à du tribalisme, comme si l’équipe était « C# »
      Je vois rarement ce genre de culture dans d’autres écosystèmes de langages, et j’ai l’impression que si F# avait appartenu à un autre écosystème que .NET, il aurait peut-être prospéré depuis longtemps
    • J’aimerais aussi beaucoup avoir les types d’unités de mesure
      Cela rendrait la maintenance du code d’ingénierie ou scientifique beaucoup plus simple
    • Avec OneOf[0] et Dunet[1], on peut déjà introduire assez facilement des unions discriminées
      Exemple d’utilisation concrète : https://chrlschn.dev/blog/2024/07/csharp-discriminated-union...
      [0] https://github.com/mcintyre321/OneOf
      [1] https://github.com/domn1995/dunet
    • C’est un secret de Polichinelle que F# sert de terrain d’expérimentation pour les fonctionnalités de C# et VB.NET, et des figures officielles comme Hanselman l’ont cité à plusieurs reprises
  • Ce qui me manque le plus quand je travaille dans d’autres langages ou écosystèmes, c’est LINQ.
    Avoir ce genre de fonctionnalité dans la bibliothèque standard, c’est vraiment appréciable, et c’est conçu avec élégance compte tenu des contraintes imposées.

  • Il y a une section à ce sujet dans l’article annuel, long comme un livre, qui couvre toutes les améliorations de performance de .NET 9.
    https://devblogs.microsoft.com/dotnet/performance-improvemen...
    Curieusement, HN n’a pas autorisé une nouvelle soumission, donc l’article n’est jamais arrivé en page d’accueil et a été enterré.

  • Une fois qu’on s’est habitué à LINQ, et qu’on travaille généralement dans des domaines où LINQ brille, on n’a plus envie de revenir à autre chose.

    • Mes amis, ne devenez pas dépendants de LINQ.
      LINQ vous retiendra, et vous finirez par en vouloir aux environnements qui n’en disposent pas.
    • Cela dit, ça reste moins puissant que quelque chose comme polars.
  • Je me demande s’il existe un livre ou un tutoriel complet de qualité pour apprendre le développement web de bout en bout avec dotnet.
    Ce que j’ai trouvé était pour la plupart trop basique, trop ancien ou de faible qualité.

    • Dans le développement web .NET, la nouveauté en vogue aujourd’hui, c’est Blazor, mais en dehors de la sphère des blogs Microsoft, il n’est pas très populaire, et je doute qu’il le devienne.
      Personnellement, je pense qu’il suivra la même trajectoire que Silverlight.
      Les anciennes technologies sont toujours présentes dans .NET 9, fonctionnent encore et sont maintenues.
      Faire du développement web en .NET aujourd’hui consiste généralement à créer des API HTTP/JSON/REST et à les connecter au framework frontend de son choix.
      Dans mon cas, j’utilise React ou NextJS.
      Les bons mots-clés de recherche sont ASP.NET WebApi, ou plus moderne, ASP.NET Minimal API.
      Le rendu côté serveur en .NET MVC avec Razor reste également possible.
      Comme c’est le langage de balisage d’ASP.NET MVC, il faut chercher “ASP.NET MVC Razor”.
    • Je me suis récemment intéressé au développement web en C#.
      Pour le meilleur ou pour le pire, ASP.NET semble être de facto la bonne réponse pour créer des applications web en .NET.
      Le manque d’alternatives est un peu suspect, certes.
      J’ai écouté un podcast avec Andrew Lock, l’auteur de “ASP.NET Core in Action”, et il avait l’air de bien maîtriser le sujet.
      Je n’ai pas encore lu le livre, mais c’est peut-être celui que vous cherchez.
      1: https://dotnetcore.show/season-6/navigating-the-aspnet-core-...
      2: https://www.manning.com/books/asp-net-core-in-action-third-e...
    • C’est un peu plus marginal, mais la combinaison F# et Fable est très puissante.
      Côté serveur, on peut faire tourner Giraffe au-dessus d’ASP.NET ; c’est une couche de programmation fonctionnelle avec des performances proches de celles de C#.
      Côté frontend, on peut écrire du React dans un vrai langage de programmation fonctionnelle.
      Évidemment, on peut aussi partager du code F# entre le frontend et le backend.
    • J’ai appris en pratiquant, mais voici quelques ressources qui peuvent servir de référence.
      Côté livres, il y a “C# 12 and .NET 8 - Modern Cross-Platform Development Fundamentals - Eighth Edition: Start building websites and services with ASP.NET Core 8, Blazor, and EF Core 8” de Mark J Price, ainsi que “Web API Development with ASP.NET Core 8: Learn techniques, patterns, and tools for building high-performance, robust, and scalable web APIs” de Xiaodi Yan.
      Pour les tutoriels, il y a les séries YouTube de IAmTimCorey et Shawn Wildermuth.
    • Pour une UI rendue côté serveur, cherchez des ressources utilisant Razor, et évitez au début les contenus sur Blazor.
      Pour une combinaison backend .NET et frontend JS, cherchez des ressources sur Minimal API.
      MVC est très bien aussi, mais il porte beaucoup de bagage de rétrocompatibilité, ce qui a conduit à l’apparition de Minimal API.
  • Il doit y avoir mieux que cet amas de spaghettis d’annotations.
    Chaque fois que je regarde du code .NET moderne, j’ai mal aux yeux.

    • Ces attributs correspondent à la bibliothèque de benchmarking utilisée dans l’article.
      Le code de tests unitaires et de benchmarks finit souvent par ressembler plus ou moins à des spaghettis.
      Cela dit, je ne laisserais pas passer une PR qui ferait ça dans de la vraie logique métier.
      Si vous détestez vraiment ça, vous pouvez aussi utiliser des choses comme AspNetCore sans toucher au moindre attribut.
    • Je ne sais pas quel code .NET vous regardez.
      Personnellement, j’utilise très peu les attributs.
  • Le fait que davantage d’optimisations soient possibles quand une chaîne se termine par des méthodes comme Count(), First(), Last(), ElementAt() ou Sum(), et que par exemple OrderBy(criteria).First() puisse être optimisé pour s’exécuter comme Min(criteria), peut être utile.
    Mais il vaut mieux écrire du meilleur code dès le départ.
    Pour des chaînes générées dynamiquement, c’est intéressant, mais dans du code écrit à la main, ce genre d’opération ressemble à une forme de renforcement positif un peu tordu.
    La bibliothèque reconnaît un motif inefficace et le corrige à votre place.
    J’espère au minimum qu’il existe un retour suggérant d’améliorer le code sous-jacent.