.NET 9.0 Preview 3: Vermischte Kleinigkeiten

Auch die dritte Vorschauversion auf .NET 9.0 enthält nur eine Sammlung kleinerer Neuerungen in den Basisklassen, ASP.NET Core und Entity Framework Core.

In Pocket speichern vorlesen Druckansicht 4 Kommentare lesen
.Net-Schild

(Bild: Pincasso/Shutterstock.com)

Lesezeit: 8 Min.
Von
  • Dr. Holger Schwichtenberg
Inhaltsverzeichnis

Microsoft hat .NET 9.0 Preview 3 veröffentlicht. Wie bereits in den ersten beiden Vorschauversionen auf .NET 9.0 – Preview 1 und Preview 2 – sind die Menge und die Tragweite der Neuerungen überschaubar, verglichen mit den großen Neuerungen, die es in den letzten Versionen von .NET auch schon in den ersten Vorschauversionen gab.

Immerhin hat Microsoft mittlerweile auf die Kritik der schlechten Kommunikation reagiert und gestaltet die Release Notes nun zielgerichteter (siehe Abbildung 1). Zu den früher etablierten Blogeinträgen im .NET-Blog ist Microsoft jedoch nicht zurückgekehrt.

Die Release Notes von .NET 9.0 Preview 3 heben die Neuerungen deutlicher hervor (Abb. 1).

(Bild: Dr. Holger Schwichtenberg)

Bei der Datenstruktur System.TimeSpan, die es seit der ersten Version des .NET Framework aus dem Jahr 2002 gibt, war bisher eine Herausforderung, dass die Konvertierungsmethoden FromMicroseconds(), FromSeconds(), FromMinutes(), FromHours() und FromDays() als Parameter einen double-Wert erwarteten. Der double-Typ ist als Fließkommazahl aber ungenau. Microsoft führt daher in .NET 9.0 zusätzlich neue Varianten der From-Methoden ein, die Ganzzahlen als Parameter erhalten:

  • public static TimeSpan FromDays(int days);
  • public static TimeSpan FromDays(int days, int hours = 0, long minutes = 0, long seconds = 0, long milliseconds = 0, long microseconds = 0);
  • public static TimeSpan FromHours(int hours);
  • public static TimeSpan FromHours(int hours, long minutes = 0, long seconds = 0, long milliseconds = 0, long microseconds = 0);
  • public static TimeSpan FromMinutes(long minutes);
  • public static TimeSpan FromMinutes(long minutes, long seconds = 0, long milliseconds = 0, long microseconds = 0);
  • public static TimeSpan FromSeconds(long seconds);
  • public static TimeSpan FromSeconds(long seconds, long milliseconds = 0, long microseconds = 0);
  • public static TimeSpan FromMilliseconds(long milliseconds, long microseconds = 0);
  • public static TimeSpan FromMicroseconds(long microseconds);

Das folgende Beispiel zeigt die größere Genauigkeit der neuen Überladungen:

// bisher
TimeSpan timeSpan1 = TimeSpan.FromSeconds(value: 101.832);
Console.WriteLine($"timeSpan1 = {timeSpan1}"); // timeSpan1 = 00:01:41.8319999
 
// neu
TimeSpan timeSpan2 = TimeSpan.FromSeconds(seconds: 101, milliseconds: 832);
Console.WriteLine($"timeSpan2 = {timeSpan2}"); // timeSpan2 = 00:01:41.8320000

In .NET 9.0 Preview 1 hatte Microsoft die aus dem klassischen .NET Framework bekannte Möglichkeit wieder eingeführt, dynamisch zur Laufzeit erstellte Assemblies ins Dateisystem oder einen beliebigen Stream zu persistieren. In Preview 3 hat sich aber nun die API geändert. Anstelle der zuvor verwendeten Klasse AssemblyBuilder

AssemblyBuilder ab = AssemblyBuilder.DefinePersistedAssembly(new AssemblyName("Math"), typeof(object).Assembly);

verwenden Entwicklerinnen und Entwickler nun die neue Klasse PersistedAssemblyBuilder:

PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("Math"), typeof(object).Assembly);

Das folgende Listing zeigt ein Beispiel zum Einsatz von PersistedAssemblyBuilder. Ein darüber hinausgehendes Beispiel zur Anpassung der Metadaten wie etwa dem Einstiegspunkt demonstriert Microsoft in den Release Notes.

string assemblyPath = Path.Combine(System.AppContext.BaseDirectory, "Math.dll");
 
  PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("Math"), typeof(object).Assembly);
  TypeBuilder tb = ab.DefineDynamicModule("MathModule").DefineType("MathUtil", TypeAttributes.Public | TypeAttributes.Class);
 
  MethodBuilder mb = tb.DefineMethod("Sum", MethodAttributes.Public | MethodAttributes.Static,
      typeof(int), [typeof(int), typeof(int)]);
  ILGenerator il = mb.GetILGenerator();
  il.Emit(OpCodes.Ldarg_0);
  il.Emit(OpCodes.Ldarg_1);
  il.Emit(OpCodes.Add);
  il.Emit(OpCodes.Ret);
 
  tb.CreateType();
  Console.WriteLine("Speichere Assembly unter: " + assemblyPath);
  ab.Save(assemblyPath); // Speichern ins Dateisystem oder einen Stream
Online-Konferenz zu .NET 9.0 am 19. November

(Bild: Dmytro Vikarchuk/Shutterstock)

In der Online-Konferenz betterCode() .NET 9.0 am 19. November 2024 von iX und dpunkt.verlag werden .NET-Experten von www.IT-Visions.de den fertigen Stand von .NET 9.0 anhand von Praxisbeispielen präsentieren. Dazu zählen die Neuerungen bezüglich des .NET 9.0 SDK, C# 13.0, ASP.NET Core 9.0, Blazor 9.0, OR-Mapping mit Entity Framework Core 9.0, Windows Forms 9.0, WPF 9.0, WinUI, Cross-Plattform-Entwicklung mit .NET MAUI 9.0 und ein Ausblick auf .NET 10.0.

Der Ticketverkauf ist bereits gestartet: Vor Bekanntgabe des Programms sind vergünstigte Blind-Bird-Tickets erhältlich.

Dass .NET beim Behandeln von Laufzeitfehlern langsam ist, ist seit vielen Jahren bekannt. Daher gehört die Vermeidung von Laufzeitfehlern zu den Best Practices. Insbesondere sollte man Laufzeitfehler nicht als Ersatz für Kontrollflussanweisungen verwenden, etwa um bei ungültigen Werten eine Schleife oder Unterroutine zu verlassen. Microsoft hat aber laut eigener Aussage in den Release Notes zur .NET Runtime die Behandlung von Laufzeitfehlern um den Faktor zwei bis vier gesteigert. Das gilt für Windows x64, Windows ARM64, Linux x64 und Linux ARM64, aber nicht für 32-Bit-Windows. Einige verbliebene Fehler dabei sollen in Preview 4 behoben sein. Entwicklerinnen und Entwickler können via Umgebungsvariable

DOTNET_LegacyExceptionHandling = 1

oder Projektdateieinstellung

<RuntimeHostConfigurationOption Include="System.Runtime.LegacyExceptionHandling" Value="true" />

die Laufzeitumgebung dazu zwingen, das alte, langsamere Fehlerbehandlungsverfahren einzusetzen.

Der in .NET 8.0 eingeführte, prägnantere Terminal Logger (zum Beispiel bei dotnet build /tl und msbuild /tl) zeigt nun Fehlermeldungen mit Zeilenumbrüchen besser an und zudem am Ende seiner Ausgabe eine Zusammenfassung der Anzahl der Fehler und Warnungen.

Was nicht in der Dokumentation steht, aber aus den Screenshots von Microsoft (siehe Abbildungen 2 und 3) hervorgeht und sich im Schnelltest bestätigte: Bei dotnet build kann man nun auf den Parameter /tl beziehunsgweise -tl verzichten, um den Terminal Logger zu aktivieren. Bei MSBuild ist aber ohne diesen Parameter auch im .NET 9.0 SDK weiterhin der klassische Logger aktiv.

Terminal Logger in .NET 8.0 (Abb. 2)

(Bild: Microsoft)

Terminal Logger in .NET 9.0 (Abb. 3)

(Bild: Microsoft)

In Entity Framework Core 8.0, erschienen im November 2023, hat Microsoft die Unterstützung für den Microsoft-SQL-Server-Datentyp hierarchyid eingeführt, in Form der .NET-Klasse HierarchyId. Seit Version 9.0 Preview 3 können Entwicklerinnen und Entwickler HierarchyId-Instanzen nicht nur wie bisher aus einer Zeichenkette (zum Beispiel "/4/1/3/1/2/"), sondern auch typsicherer auf Basis einer anderen HierarchyId und Ganzzahlen erstellen. Das folgende Listing zeigt den Einsatz der neuen Überladungen von HierarchyId.Parse().

 for (int j = 1; j < 5; j++)
   {
    // alt
    var level2 = $"{startLevel}{i}/{j}/"; 
    var hid2Alt = HierarchyId.Parse(level2);
    // neu
    var hid2Neu = HierarchyId.Parse(abteilungsleiter.Level, j);

    var n = new Employee(hid2Neu, f.Name.FullName(), f.Random.Number(1970, 2023));
    n.Company = company;
    ctx.Add(n);
    Console.WriteLine("   " + n.ToString());
    var c3 = ctx.SaveChanges();
   }

Entity Framework Core erzeugt zur Laufzeit einigen Programmcode für das Mapping eines Objektmodells auf ein Datenbankschema. Diese Laufzeitcodegenerierung kann bei großen Objektmodellen zeitaufwendig sein. Zudem ist Laufzeitcodegenerierung nicht kompatibel mit dem Ahead-of-Time-Compiler Native AOT, den es seit .NET 7.0 gibt und den Microsoft in .NET 9.0 auch mit Entity Framework Core ermöglichen will.

Seit Version 6.0 des modernen objektrelationalen Mappers gibt es bereits kompilierte Modelle, bei denen ein Teil der Laufzeitcodegenerierung zur Entwicklungszeit stattfindet. Dazu musste man zuerst einen Kommandozeilenbefehl ausführen

dotnet ef dbcontext optimize

beziehungsweise in der PowerShell:

Optimize-DbContext

Danach musste man im Programmcode noch einen Methodenaufruf in OnConfiguring() ergänzen:

.UseModel(Kontextname.Instance)

Zudem mussten Entwicklerinnen und Entwickler die kompilierten Modelle an der Kommandozeile immer wieder neu erzeugen, wenn es Änderungen am Objektmodell oder der Kontextklasse gab.

Alle diese Schritte können nun entfallen. Das Kompilieren lässt sich mit einem neuen MSBuild-Task <EFOptimizeContext> automatisieren. Dazu muss man das neue NuGet-Paket Microsoft.EntityFrameworkCore.Tasks einbinden und dann in der Projektdatei die Einstellung <EFOptimizeContext> auf true setzen:

<PropertyGroup>
  <EFOptimizeContext>true</EFOptimizeContext>
</PropertyGroup>

Bei jedem Übersetzungsvorgang sieht man dann:

Optimizing DbContext...

Anpassungen der Modellkompilierung sind über weitere Einstellungen wie EFStartupProject, DbContextName und EFTargetNamespace möglich.

Der Aufruf UseModel() ist seit Entity Framework Core 9.0 Preview 3 nicht mehr notwendig, sofern das kompilierte Modell in der gleichen Assembly liegt wie die Kontextklasse. Dies gilt unabhängig davon, ob man den MSBuild-Task einsetzt oder die Modellkompilierung weiterhin von Hand anstößt.

ASP.NET Core kann zur Entwicklungszeit eine Fehlerseite anzeigen: app.UseDeveloperExceptionPage(). Dieses Feature gibt es seit .NET Core 1.0. In .NET 9.0 wurde die Registerkarte Routing aber um weitere Informationen über den Endpunkt ergänzt (siehe Abbildung 4).

Die Klasse TypedResults für ASP.NET-Core-basierte WebAPIs kann nun auch den Statuscode 500 ("Internal Server Error") zurückliefern:

var app = WebApplication.Create();
app.MapGet("/", () => TypedResults.InternalServerError("Something went wrong!"));
app.Run();

Bei Blazor wurde die Klasse KeyboardEventArgs um die Eigenschaft IsComposing erweitert. Damit lässt sich abfragen, ob der Tastaturanschlag Teil einer zusammengesetzten Tastatureingabe ist, etwa bei Eingabe von Akzenten ("`" und "e" für "è").

Erweiterte Registerkarte "Routing" in der Entwicklungszeitfehlerseite bei ASP.NET Core (Abb. 4)

(Bild: Dr. Holger Schwichtenberg)

.NET 9.0 enthält in den ersten drei Preview-Versionen wenig Neuerungen und auch die veröffentlichten Pläne von Microsoft fallen spärlich aus im Vergleich zu dem, was in den letzten Jahren kam (siehe beispielsweise die Roadmap für ASP.NET Core 9.0). Das Entity-Framework-Core-Team hat auch fünf Monate nach dem Erscheinen von .NET 8.0 und sieben Monate vor dem Erscheinen von .NET 9.0 immer noch keinen Plan für das kommende Release veröffentlicht (siehe Abbildung 5).

Das Entity-Framework-Core-Team hat keinen Plan für Version 9.0 veröffentlicht (Abb. 5).

(Bild: Microsoft)

Interessanterweise kommt zudem keine der drei neuen Funktionen in ASP.NET Core 9.0 Preview 3 von Microsoft selbst, sondern alle dokumentierten Neuheiten in dieser Version sind Beiträge aus der Community.

Das alles bietet eine Steilvorlage für diejenigen, die schon seit Jahren rufen: ".NET ist tot".

Man sollte allerdings bedenken, dass .NET 9.0 nur ein Standard-Term Release ist und man vielleicht daher weniger erwarten sollte als bei einem Long-Term Release wie .NET 8.0. Zudem findet vom 21. bis 23. Mai 2024 wieder Microsofts jährliche Entwicklerkonferenz Build statt, die bisher oft ein Aufhänger für die Bekanntgabe neuer Features war.

(mai)