-.NET 9.0 verkürzt in mehreren allgemeinen LINQ-Szenarien die Ausführungszeit gegenüber .NET 8 deutlich und beseitigt in einigen Benchmarks sogar Allokationen
- Eine der zentralen Verbesserungen ist, dass beim Iterieren über Arrays oder
List<T>mitTryGetSpan()einReadOnlySpan<T>geholt wird, um die Kosten der Iteration zu senken TryGetSpan()erkenntTSource[]undList<TSource>per Typvergleich, aber die Methode, fürList<T>einen Span auf das interne Array zu holen, ist eine Unsafe-basierte Optimierung, die bei Kapazitätsänderungen ungültig werden kann- Das LINQ in .NET 9 erkennt gängige Aufrufketten, erstellt dafür spezielle Iteratoren und wendet in terminalen Methoden wie
Count(),First(),Last(),ElementAt()undSum()zusätzliche Optimierungen an - Schon durch einfache Migration und Neukompilierung lassen sich einige LINQ-Performancegewinne erzielen; außerdem sind Optimierungen wie SIMD-Nutzung und frühes Erkennen leerer Sequenzen enthalten
Warum Arrays und Listen schneller iteriert werden
- Der erste Benchmark speichert
Enumerable.Range(1, 10_000).ToArray()alsIEnumerable<int>und führt dannCount,All,Any,First,SingleundLastaus, um .NET 8 und .NET 9 zu vergleichen - Verwendet wird BenchmarkDotNet; das Projekt sollte auf
net8.0;net9.0zielen und im Release-Modus kompiliert werden - In .NET 9 sinkt bei mehreren Methoden die Laufzeit deutlich, und Allokationen verschwinden
LinqCount: von 16,198.490 ns auf 3,043.563 ns, von 32 B Allokation auf keine AllokationLinqAny: von 17,096.735 ns auf 2,483.927 ns, von 32 B Allokation auf keine AllokationLinqFirst: von 15,289.747 ns auf 2,243.341 ns, von 32 B Allokation auf keine AllokationLinqSingle: von 21,684.114 ns auf 4,884.329 ns, von 32 B Allokation auf keine AllokationLinqAll: von 10.588 ns auf 2.562 ns, von 32 B Allokation auf keine AllokationLinqLast: von 15.967 ns auf 6.918 ns
Den Unterschied macht TryGetSpan()
- Ein Hauptgrund für den Performancegewinn ist die Verwendung von
TryGetSpan() - Wenn das zu durchlaufende Enumerable ein Array oder eine Liste ist, gibt
TryGetSpan()einReadOnlySpan<T>zurück und ermöglicht dadurch schnellere Iteration - Der zentrale Verzweigungscode prüft
source.GetType() == typeof(TSource[])odersource.GetType() == typeof(List<TSource>)und holt dann den Span- Arrays werden mit
Unsafe.As<TSource[]>(source)verarbeitet - Listen verwenden
CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source)), um einen Span auf das interne Array zu holen
- Arrays werden mit
- Im Code wird
source.GetType()zweimal aufgerufen und nicht per Cast mit anschließender Nullprüfung gearbeitet, aber .NET-Performanceexperten haben diesen Ansatz mit Blick auf Optimierungen des C#-Compilers und des JIT gewählt - In einem hochoptimierten .NET-Stack können Mikro-Optimierungen zu Ergebnissen führen, die der äußeren Erscheinung widersprechen
Einschränkungen von CollectionsMarshal.AsSpan()
List<TSource>referenziert intern ein Array, und wenn die Kapazität der Liste wächst oder schrumpft, wird ein neues Array erzeugt und referenziertCollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source))holt einSpan<TSource>aus diesem internen Array- Wenn sich die Kapazität der Liste auf irgendeine Weise ändert, kann das so ermittelte Array ungültig werden
- Wegen dieser Einschränkung können sich einige
Enumerable-Operationen mit verzögerter Iteration wieyieldnur schwer auf diese Optimierung stützen - Schon der Name
System.Runtime.CompilerServices.Unsafemacht dieses Risiko deutlich
Wo TryGetSpan() aufgerufen wird
- Mit NDepend wurde
System.Linq.dllgescannt, um direkte und indirekte Aufrufer vonTryGetSpan()zu ermitteln - Der analysierte Assembly-Pfad lautet
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.0-rc.1.24431.7\System.Linq.dll - Von
TryGetSpan()aus wurde eine Code-Abfrage erstellt, um Aufrufer zu identifizieren, und 56 passende Methoden wurden in einen Abhängigkeitsgraphen exportiert - Viele Standardmethoden von
Enumerableversuchen Span-Iteration, wenn die Collection ein Array oder eine Liste ist - Da das Festhalten am internen Array einer Liste jedoch nicht sicher ist, bleiben für Operationen mit notwendiger verzögerter Ausführung Einschränkungen bestehen
Optimierung auf Basis spezieller Iteratoren
- Der zweite Benchmark verwendet Beispiele aus dem PR Consolidate LINQ’s internal IIListProvider/IPartition into base Iterator class
- Getestet werden unter anderem
Distinct().First(),Append().Select().Last(),Reverse().Count(),DefaultIfEmpty().Select().ElementAt(),Skip().Take().ElementAt(),Union().First()undSelect().Where().Select().Sum() - In .NET 9 werden einige Aufrufketten extrem viel schneller
DistinctFirst: von 65.318 ns auf 11.192 ns, von 328 B Allokation auf keine AllokationAppendSelectLast: von 4,122.007 ns auf 2.661 ns, von 144 B Allokation auf keine AllokationDefaultIfEmptySelectElementAt: von 4,090.818 ns auf 5.724 ns, von 144 B Allokation auf keine AllokationRangeUnionFirst: von 66.309 ns auf 6.193 ns, von 344 B Allokation auf keine AllokationListSkipTakeElementAt: von 6.268 ns auf 2.916 nsRangeReverseCount: von 11.024 ns auf 6.134 ns
- Umgekehrt wurde
SelectWhereSelectSumlangsamer: von 3,959.622 ns unter .NET 8 auf 4,460.008 ns unter .NET 9; auch die Allokation von 112 B bleibt bestehen
Erkennung häufiger LINQ-Ketten
- Das .NET-Performance-Team hat den Code so entworfen, dass häufige LINQ-Aufrufketten erkannt werden
- Wird eine bestimmte Kette erkannt, wird ein spezieller Iterator erzeugt, der den Ablauf effizienter verarbeitet
- Endet die Kette mit Methoden wie
Count(),First(),Last(),ElementAt()oderSum(), sind zusätzliche Optimierungen möglich - So kann zum Beispiel
OrderBy(criteria).First()so optimiert werden, dass es wieMin(criteria)ausgeführt wird
Struktur von Iterator<T> und abgeleiteten Klassen
- Innerhalb von LINQ gibt es die abstrakte Basisklasse
Iterator<T>und 40 abgeleitete Klassen - Diese Klassen sind alle in die Klasse
Enumerablegeschachtelt Iterator<T>ist zwar eine abstrakte Klasse, die Methoden sind jedoch virtual, sodass abgeleitete Klassen nur die jeweils benötigten Methoden überschreiben- Diese Struktur bildet die Grundlage dafür, spezialisiertes Verhalten pro Aufrufkette abzulegen
Beispiel ListWhereSelectIterator<TSource, TResult>
ListWhereSelectIterator<TSource, TResult>verarbeitet die KetteWhere(...).Select(...)auf Listen mit einem einzigen Iterator- Dieser Iterator wird im
Select()-Override vonListWhereIterator<TSource, TResult>erzeugt ListWhereIterator<TSource>wird erstellt, wennEnumerable.Where()prüft, ob die Quelle einList<TSource>istListWhereSelectIterator<TSource, TResult>überschreibt keine Methoden wieTryGetFirst()oderTryGetLast()- Der Kern des Performancegewinns ist, dass die auf Listen sehr häufige Kette
Where(...).Select(...)nicht mit zwei Iteratoren, sondern mit einem einzigen Iterator zusammengeführt wird- In
MoveNext()werden die beiden Delegates_predicateund_selectorgemeinsam aufgerufen
- In
Beispiel IListSkipTakeIterator<TSource>
IListSkipTakeIterator<TSource>ist ein spezieller Iterator, der erzeugt wird, wenn dies möglich istMoveNext()verwendet_state - 1als nullbasierten Index der Liste- Ein separates Indexfeld wäre besser lesbar, aber um die Größe der Iterator-Felder zu verringern, wird der Wert mit Bias in
_stategespeichert - Die Optimierung dieses Iterators besteht darin, keine unnötigen Elemente außerhalb des Bereichs
_minIndexInclusivebis_maxIndexInclusivezu durchlaufen
Zusätzliche Optimierungen allein durch Migration
- In .NET 9 werden viele allgemeine LINQ-Szenarien schneller
- Um von den Verbesserungen der neuen .NET-Version zu profitieren, sind nur Migration und Neukompilierung nötig
- LINQ wird auch auf andere Weise optimiert
- Beim Summieren von Integer-Sequenzen wird, wo möglich, SIMD verwendet
- Leere Sequenzen werden früh erkannt, wodurch die Kosten der Enumeration sinken
- DeepDotnet videos können als .NET-Lernmaterial mit Scott Hanselman und Stephen Toub angesehen werden
1 Kommentare
Hacker-News-Kommentare
Der nützlichste Teil von LINQ sind meiner Ansicht nach nicht die auf Syntaxbäumen basierende Erweiterungsstruktur von
IQueryableoder die in die Sprache eingebaute Syntax, sondern die Erweiterungsmethoden vonIEnumerableFrüher wurden sie etwas verwirrend als „LINQ to Objects“ bezeichnet, und sie ermöglichen es, C# in einem funktionalen Stil knapp zu schreiben
Der Originalartikel behandelt hauptsächlich die Optimierung dieser Erweiterungsmethoden
Richtig nachvollziehen konnte ich diesen Ansatz erst, nachdem ich Haskell gelernt hatte; dabei teilt er auch einige der Vor- und Nachteile von Haskell, etwa die verzögerte Auswertung
Wenn man es gedankenlos verwendet, kann daraus schwer lesbarer und langsamer Code werden, daher würde ich es nicht empfehlen, wenn im Team niemand grundlegende funktionale Idiome und Lazy Evaluation versteht
IEnumerableundIQueryableEs ist leichter, darüber zu schlussfolgern, und auch wenn es in Dingen wie Entity Framework nicht immer die schnellste Wahl ist, ist es meistens eine ziemlich gute Option
Ich nutze auch lieber Dapper als EF
Allerdings neigen C#-Projekte dazu, absurd viele Abstraktionsschichten anzuhäufen, und „Enterprise“-Entwicklung ist meistens schwer anzusehen
Die Benennung ist an einigen Stellen etwas unüblich, aber es hat alles, was man braucht
Eric Lippert hat eine hervorragende Artikelreihe geschrieben, in der er Monaden anhand von LINQ erklärt: https://ericlippert.com/2013/04/02/monads-part-twelve/
Ich mag es nicht, wenn innerhalb der Hostsprache noch eine weitere „eingebaute“ Sprache steckt, und das Endergebnis muss am Ende ohnehin wieder auf C# zurückgeführt werden
Auch wenn man keine ORMs wie Entity Framework oder Dapper verwendet, ist es besser, Datenzugriffslogik einschließlich SQL in einem separaten abstrahierten Projekt unterzubringen
So verteilt sie sich nicht über die ganze Anwendung, und wenn irgendwann ein anderes RDBMS nötig wird, kann man es austauschen
In 20 Jahren ist das in der Praxis allerdings nur einmal passiert
Wenn Junior-Entwickler LINQ verwenden, hilft es, einen Profiler und Debugger dazuzunehmen, damit sie verstehen, was intern passiert
Manchmal ist es auch sinnvoll, sie zuerst eine Lösung mit
for-Schleifen und normaler C#-Logik schreiben zu lassen und diese dann mit einer LINQ-Implementierung zu vergleichen, damit sie die Vor- und Nachteile beider Ansätze sehenDie Abfragesyntax ist nicht fest auf
IEnumerableverdrahtet; das ist nur das Standardverhalten, und man kann sie fast überall einsetzenSie funktioniert ein wenig ähnlich wie Operator Overloading
[1]: https://github.com/acple/ParsecSharp/blob/da8d0cb9ec39e28dd9...
Ich verstehe nicht, warum das dotnet-Team nicht mehr Ressourcen und Zeit in die Tooling-Landschaft investiert
Es braucht doctest und Dokumentationsgenerierung, bessere und schnellere Unit-Tests direkt neben dem eigentlichen Code, besseren Zugang zum Source Code, eine Umgebung, in der man mit F12 nicht erst DLLs dekompilieren muss, sowie einen zentralen Hub für Pakete und Dokumentation wie
pkg.go.devoderdocs.rsDie meisten NuGet-Pakete haben entweder gar keine Dokumentation, nur ein GitHub-README oder höchstens ein kurzes Wiki
In solchen Punkten sind andere Umgebungen wie Rust, Go, Java und Python Lichtjahre voraus
Erschreckenderweise habe ich aber auch das Gefühl, dass das ziemlich nah an der Wahrheit ist
Beispiel: https://learn.microsoft.com/en-us/dotnet/api/system.string.s...
Für NuGet-Paketquellen ist das mit aktiviertem Source Link ebenfalls leicht möglich, aber das ist noch eine relativ neue Funktion, daher ist sie noch nicht in allen Paketen umgesetzt
Der Großteil des C#-Codes dürfte noch immer in Unternehmen als Closed Source geschrieben werden
Wenn Microsoft den offenen Kurs der letzten Jahre beibehält, wird es mit der Zeit wohl besser werden
Einige dieser Funktionen werden von Tools wie Resharper bereitgestellt, und ich frage mich, ob es eine ausdrückliche oder stillschweigende Übereinkunft gibt, sich nicht gegenseitig ins Revier zu kommen
Ehrlich gesagt war der Großteil der Dokumentation, die ich in C#-Projekten gesehen habe, von geringer Qualität, sodass ich am Ende doch den Source Code lese
Meine Erfahrung ist, dass viele Autocomplete-Tools beim Lesen kaum helfen und nur beim Schreiben nützlich sind
https://github.com/EWSoftware/SHFB
Ob ich das empfehlen würde, weiß ich nicht
Ich habe es selbst ausprobiert und später wieder rückgängig gemacht; offenbar wurden die Tests langsamer, möglicherweise wegen schlechterem Caching der Build-Artefakte
Treffender wäre wohl nicht „LINQ-Performanceverbesserungen“, sondern „Performanceverbesserungen der eigenen
List-Implementierung“Microsoft scheint seine Zeit eher darauf zu verwenden, die Teile zu verbessern, die es selbst braucht, statt allgemeine Verbesserungen voranzutreiben
LINQ, insbesondere die Abfragesyntax und nicht nur Methodenerweiterungen, braucht Investitionen
Vor allem sind weniger Lambda-Allokationen nötig und, wenn möglich, eine Reduzierung von Lambdas bereits zur Compile-Zeit
Es braucht Strategien wie lokale Lambdas als Werttypen oder zumindest Ansätze, bei denen Lambda-Allokationen nicht weiter so viel Overhead verursachen wie heute
Auch LINQ-Variablen sollten inzwischen Wildcards (
_) unterstützen, aber das wurde bei der Einführung für Lambdas komplett ignoriertAußerdem sollte man als letztes Element eines LINQ-Ausdrucks statt
select ...auch geliftete Typen wieIEnumerableoderOptionverwenden könnenIn bestimmten Anwendungsfällen erzeugt
selectunnötigen Overhead und schränkt auch Dinge wie LINQ-Ausdrücke mit Endrekursion einBibliotheken wie meine, die voll auf LINQ setzen, aber weder
IEnumerable,IQueryablenoch LINQ-Erweiterungen verwenden, werden weiterhin ignoriertDer Grund ist, dass Microsoft sich nur auf Performanceverbesserungen für die eigenen Projekte konzentriert
Ein gutes Beispiel dafür ist die verbesserte Lambda-Inferenz
Sie wurde vorgezogen, weil sie für die Minimal API von ASP.NET Core gebraucht wurde
Ein erheblicher Teil der Sprach- und Framework-Features scheint eher von Microsofts internen Anforderungen getrieben zu sein als von den Bedürfnissen der Community
Am schlimmsten ist, dass die Menge an magischen Methoden immer weiter wächst, nicht nur bei LINQ-Erweiterungen wie
Select,SelectManyundWhere, sondern auch bei Dingen wieGetAwaiterStatt die Magie durch wirklich notwendige Eigenschaften höherer Kinds aufzulösen, fügt Microsoft eher Features für sich selbst hinzu, vor allem für den Compiler
Deshalb bleibt alles schwach typisiert, und der Compiler kann es nur ungefähr erkennen
LINQ ist eines der zentralen Unterscheidungsmerkmale zwischen Sprachen, wurde aber seit C# 3 fast völlig vernachlässigt
Es ist wirklich schade, dass LINQ noch immer nur als nützlich für Listeniteration angesehen wird, insbesondere für die Iteration über die eigene Listenimplementierung
Für die Performanceverbesserungen an sich bin ich dankbar, und sie werden vielen Nutzern helfen, aber der Fokus ist immer so eng gesetzt, dass das Potenzial begrenzt bleibt
[1] https://github.com/louthy/language-ext/
dotnet/runtimezu eröffnen oder einen PR zu schickenEin großer Teil der im Artikel behandelten LINQ-Performanceverbesserungen ist genau auf diesem Weg eingeflossen
Es gibt viele
using-Anweisungen, was für Menschen, die verstehen, wie man Projekte in kleinere Einheiten aufteilt und Verantwortlichkeiten trennt, kein großes Problem istDie meisten Entwickler strukturieren ihre Projekte aber nicht so, und solche Kleinigkeiten können für durchschnittliche Entwickler eine Hürde sein
Gerade Junior-Entwickler tun sich oft schon mit der Standard-LINQ-Syntax und den Methoden schwer, insbesondere auch bei der Performance
Gut, dass das im README erwähnt wird
Oft sind Leute nur damit beschäftigt, eine Bibliothek zu „verkaufen“, aber mir gefällt, dass hier tatsächlich steht, worin ihre Stärken liegen und wofür sie gedacht ist
Auch der Hinweis, dass sie unidiomatisch ist, kann für Leute, die C#/.NET lernen, ein Problem sein
Microsoft wird vermutlich wollen, dass Tools und Sprache bestimmten Konventionen folgen, und eine Benennung, die sich natürlicher an funktionaler Programmierung orientiert, könnte aus Microsofts Sicht ein ziemlich großes Hindernis sein, wenn über Verbesserungen nachgedacht wird
Ich habe dem Repository einen Stern gegeben und großes Interesse an dem, was du gebaut hast
In einigen größeren Anwendungen, die ich kürzlich erstellt habe, habe ich einen
Result-Typ verwendet, derOptiongrob zu ähneln scheintAls ich mir die Bibliothek dann erneut ansah, hatte ich das Gefühl, dass ich zwar dachte, über funktionale Programmierung ziemlich viel zu wissen, es in Wirklichkeit aber doch nicht tue
In C# bin ich ziemlich gut und habe auch komplexe Dinge umgesetzt, aber funktionale Programmierung ist trotz vieler Lektüre immer noch schwer zu verinnerlichen, und F# For Fun And Profit war für mich noch am verständlichsten
Das heißt am Ende nicht, dass du etwas falsch machst
Microsoft wird sich an durchschnittlichen oder eher unerfahrenen Entwicklern orientieren, weil sie den Großteil des eigenen Ökosystems ausmachen
Ich hoffe, dass diese Bibliothek interne Verbesserungen bekommen kann
Man sieht, wie viel Zeit hineingeflossen ist, und allein die Zahl der GitHub-Sterne ist ein starkes Signal dafür, dass es Menschen gibt, die sie tatsächlich verwenden und davon profitieren
Falls es so klang, als würde ich das abwerten, tut mir das leid, aber die Arbeit ist interessant, und es sieht so aus, als gäbe es genug Dokumentation, um unbekannte Konzepte langsam zu lernen
Je mehr C# aus F# übernimmt, desto besser
Ich warte darauf, dass discriminated unions endlich in C# kommen, damit man Domain-Modeling richtig machen kann
Jedes Mal denke ich dann: Warum nicht einfach F# verwenden?
C# hat über Jahre hinweg nur aufgeholt
Wenn F# viele Innovationen im .NET-Ökosystem vorangetrieben hat und bei Features jahrelang voraus war, frage ich mich, warum man diese Arbeit nicht durch tatsächliche Nutzung belohnt
Wenn man eine bestimmte Entwicklungsrichtung für Sprachen möchte, sollte man sie durch echte Entscheidungen fördern
Im Java-Ökosystem hat das auf diese Weise funktioniert, und inzwischen wird auch Java besser
Wenn der Markt wächst, kann ein positiver Rückkopplungseffekt entstehen, bei dem auch mehr Engineering-Aufwand investiert wird
Wenn man über Jahre Foren liest, hat man im C#-Lager oft den starken Eindruck, dass die Leute „einfach“ in ihrem Lager bleiben und warten wollen
Es wirkt fast ein wenig tribalistisch, als wäre ihr Team einfach „C#“
In anderen Sprachökosystemen sehe ich diese Kultur kaum, und ich habe den Eindruck, dass F# längst aufgeblüht wäre, wenn es nicht Teil von .NET, sondern eines anderen Ökosystems wäre
Das würde die Wartbarkeit von Ingenieur- und Wissenschaftscode deutlich verbessern
Ein praktisches Beispiel: https://chrlschn.dev/blog/2024/07/csharp-discriminated-union...
[0] https://github.com/mcintyre321/OneOf
[1] https://github.com/domn1995/dunet
Wenn ich in anderen Sprachen oder Ökosystemen arbeite, vermisse ich am meisten LINQ.
So eine Funktionalität in der Standardbibliothek zu haben, ist wirklich großartig, und sie ist innerhalb der gegebenen Einschränkungen elegant entworfen.
Es gibt einen entsprechenden Abschnitt in einem jährlichen, buchlangen Beitrag, der alle Performance-Verbesserungen in .NET 9 behandelt.
https://devblogs.microsoft.com/dotnet/performance-improvemen...
Seltsamerweise erlaubt HN keine erneute Einreichung, daher schaffte es der Beitrag nicht auf die Startseite und ging unter.
Wenn man sich einmal an LINQ gewöhnt hat und gewöhnlich in einem Bereich arbeitet, in dem LINQ glänzt, will man nicht mehr zu anderen Ansätzen zurück.
LINQ wird euch festhalten, und ihr werdet Umgebungen ohne LINQ verfluchen.
Ich frage mich, ob es ein gutes umfassendes Buch oder Tutorial gibt, um End-to-End-Webentwicklung mit dotnet zu lernen.
Das meiste, was ich gefunden habe, war entweder zu oberflächlich, veraltet oder von geringer Qualität.
Persönlich denke ich, dass es einen ähnlichen Weg wie Silverlight gehen wird.
Auch ältere Technologien sind in .NET 9 weiterhin vorhanden, funktionieren und werden gepflegt.
Heutzutage bedeutet Webentwicklung mit .NET meistens, HTTP/JSON/REST-APIs zu bauen und sie mit einem Frontend-Framework nach Wahl zu verbinden.
Ich verwende dafür React oder NextJS.
Als Suchbegriffe eignen sich ASP.NET WebApi oder moderner ASP.NET Minimal API.
Weiterhin ist auch serverseitiges Rendering mit .NET MVC unter Verwendung von Razor möglich.
Da es sich um die Markup-Sprache von ASP.NET MVC handelt, sollte man nach „ASP.NET MVC Razor“ suchen.
Ob gut oder schlecht, ASP.NET scheint praktisch die offensichtliche Antwort darauf zu sein, wie man Webanwendungen in .NET erstellt.
Dass es nur wenige Alternativen gibt, wirkt allerdings etwas verdächtig.
Ich habe einen Podcast mit Andrew Lock, dem Autor von „ASP.NET Core in Action“, gehört, und er wirkte wie jemand, der das Thema wirklich gut kennt.
Ich habe das Buch noch nicht gelesen, aber es könnte genau das sein, wonach du suchst.
1: https://dotnetcore.show/season-6/navigating-the-aspnet-core-...
2: https://www.manning.com/books/asp-net-core-in-action-third-e...
Auf dem Server kann man Giraffe auf ASP.NET laufen lassen, eine funktionale Programmierschicht mit ähnlicher Performance wie C#.
Im Frontend kann man React in einer echten funktionalen Programmiersprache schreiben.
Natürlich kann man auch F#-Code zwischen Frontend und Backend teilen.
Als Bücher gibt es Mark J Price, „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“, sowie Xiaodi Yan, „Web API Development with ASP.NET Core 8: Learn techniques, patterns, and tools for building high-performance, robust, and scalable web APIs“.
Als Tutorials gibt es die Serien von IAmTimCorey und Shawn Wildermuth auf YouTube.
Wenn man .NET-Backend plus JS-Frontend kombinieren will, sollte man nach Material zu Minimal API suchen.
MVC ist auch gut, aber wegen der vielen Altlasten bei der Abwärtskompatibilität wurde Minimal API überhaupt erst eingeführt.
Es muss doch einen besseren Weg geben als diesen Kommentar-Nudelhaufen.
Jedes Mal, wenn ich modernen .NET-Code sehe, tun mir die Augen weh.
Unit-Tests und Benchmarking-Code sehen meistens zwangsläufig etwas nach Spaghetti aus.
Trotzdem würde ich einen PR mit so etwas im eigentlichen Business-Code nicht durchwinken.
Wenn du es wirklich hasst, kannst du auch so etwas wie AspNetCore verwenden, ohne auch nur ein einziges Attribut anzufassen.
Ich verwende fast nie Attribute.
Der Punkt, dass bei Methoden wie
Count(),First(),Last(),ElementAt()undSum()am Ende einer Chain mehr Optimierungen möglich sind und dass zum BeispielOrderBy(criteria).First()so optimiert werden kann, dass es sich wieMin(criteria)verhält, kann nützlich sein.Aber eigentlich sollte man von vornherein besseren Code schreiben.
Für dynamisch erzeugte Chains ist das vielleicht interessant, aber wenn man solche Operationen in selbst geschriebenem Code hat, fühlt es sich wie eine schiefe Form positiver Verstärkung an.
Die Bibliothek erkennt ineffiziente Muster und korrigiert sie für einen.
Ich hoffe zumindest auf Feedback, das eine Verbesserung des zugrunde liegenden Codes vorschlägt.