In Microsofts Blogeinträgen zu den aktuellen Preview-Versionen von .NET 7.0 haben sich zuletzt einige Ungereimtheiten eingeschlichen: Einige der gezeigten Codebeispiele laufen (zumindest noch) nicht. Auch wurden einige der angekündigten Features von .NET 7.0 vertagt.
Die im Blogeintrag zu Preview 4 [1] angepriesenen Eigenschaften Microseconds und Nanoseconds in den Klassen TimeStamp, DateTime, DateTimeOffset und TimeOnly gibt es auch in Preview 7 immer noch nicht. Im Blogeintrag zu Preview 6 [2] spricht Microsoft über die neuen Basistypen Int128 und UInt128. Dazu gibt es auch Seiten in der Klassendokumentation (zu System.Int128 [3] und System.UInt128 [4]), aber der Compiler findet die Klassen noch gar nicht („Error CS0234: The type or namespace name Int128 does not exist in the namespace System“), obwohl diese in der System.Runtime.dll (als Teil des .NET SDL) enthalten sein sollen, also nicht in einem getrennten NuGet-Paket. Auch ein <EnablePreviewFeatures>True</En-ablePreviewFeatures> in der Projektdatei hilft nicht.
Vorhanden ist lediglich die neue Basisdatentypklasse System.Half [5], eine Gleitkommazahl im Umfang von zwei Bytes im Wertebereich -65500 bis 65500. System.Half ist eine Ergänzung zu System.Single und System.Double, die es seit .NET Framework 1.0 (Release im Jahr 2002) gibt.
Auch bei einem weiteren im Blogeintrag angekündigten Features für Platform Invoke (P/Invoke) in Preview 7 [6] gibt es einen Fehler: Kopiert man das im Abschnitt LibraryImport P/Invoke Source Generator abgedruckte Beispiel
public static class Native
{
[LibraryImport(nameof(Native), StringMarshalling = StringMarshalling.Utf16)]
public static partial string ToLower(string str);
}
in Visual Studio, meckert der Compiler: „Error SYSLIB1050 Method ToLower is contained in a type NativeNew that is not marked partial. P/Invoke source generation will ignore method ToLower“. Richtig ist:
public static partial class Native
{
[LibraryImport(nameof(Native), StringMarshalling = StringMarshalling.Utf16)]
public static partial string ToLower(string str);
}
Auf einen passenden Blogkommentar [7] hat Microsoft bisher nicht reagiert.
Die Annotation [LibraryImport] verwendet im Gegensatz zu der schon seit .NET Framework 1.0 vorhandenen Annotation [DllImport] für den Aufruf einer nativen (plattformspezifischen) Funktion nicht mehr die Laufzeitcodegenerierung (Generierung eines Intermediate Language Stub zur Laufzeit), sondern einen Source Code Generator. Der dann bereits zur Entwicklungszeit generierte Interop-Code ist sichtbar unter / Dependencies/Analyzers/ Microsoft.Interop.LibraryImport-Generator. Das funktioniert im Gegensatz zu [DllImport] zuverlässig auch in Verbindung mit Trimming (Tree Shaking) und Ahead-of-Time-Kompilierung. [LibraryImport] erlaubt auch Parameter der neueren Typen Span<T> und ReadOnlySpan<T>. Zudem wird der Aufruf der nativen Funktion durch die Entwicklungszeitcodegenerierung beschleunigt.
Ein weiteres Beispiel zeigt Jeremy Likness im Blogeintrag zu Preview 7 [6] für das Lesen und Setzen von Dateisystemrechten auf Unix-Dateisystemen, etwa:
// Create a new directory with specific permissions
Directory.CreateDirectory("myDirectory", UnixFileMode.UserRead | UnixFile-Mode.UserWrite | UnixFileMode.UserExecute);
und
// Get the mode of an existing file
UnixFileMode mode = File.GetUnixFileMode("myFile")
Weder die Enumeration UnixFileMode noch die neuen Methode GetUnixFileMode() und SetUnixFileMode() werden vom Compiler im Namensraum System.IO gefunden, obwohl laut GitHub-Check-in [8] diese Erweiterungen in der Assembly System.Runtime.dll liegen, also im .NET SDK ohne weitere NuGet-Installation enthalten sein sollten. Auf eine entsprechende Anmerkung [9] hat Microsoft ebenfalls nicht reagiert.
Man darf sich sehr wundern über das mangelnde Qualitätsbewusstsein von Microsoft: Wieso findet man in den Blogeinträgen so viele Fehler bzw. Features, die (noch) gar nicht implementiert sind? Offensichtlich wird der Programmcode, der in den Blogeinträgen veröffentlicht wird, weder vom Autor des Beitrags noch von einem anderen Mitarbeiter geprüft. Das ist sehr ärgerlich für alle Enthusiasten, die gerne schon die neusten Funktionen aus den Previews ausprobieren, und macht auch den IT-Journalisten das Leben sehr schwer. So investiert man viele Stunden, die kein Verlag bezahlen will.
Eine Dokumentation zu allen neuen Features gibt es ja meist noch nicht in der Preview-Phase; das ist durchaus akzeptabel. Daher sind die Blogeinträge die Hauptinformationsquelle in der Preview-Phase. Sich durch ellenlange GitHub-Issues oder den Quellcode zu wühlen, kann keine Alternative sein. Hier muss Microsoft dringend die Qualität der eigenen Berichterstattung verbessern.
Darüber hinaus sei erwähnt, dass die .NET-Blog-Website selbst einen Bug hat [10]: Als ich auf der Startseite Anfang August die Seite 2 anwählte oder den Pfeil nach rechts anklickte, landete ich nicht bei den Beiträgen aus dem Juli, sondern Juni, obwohl es 11 Blogbeiträge im Juli gab, die man sieht, wenn man direkt July 2022 wählt.
In Preview 7 fehlen auch immer noch einige der zu Jahresbeginn für .NET 7.0 angekündigten Funktionen [11]. Auf GitHub kann man nachlesen, was in ASP.NET Core [12] und Entity Framework Core [13] noch bis zum endgültigen Erscheinen am 8. November 2022 kommen soll. Einige Punkte sind dort aber bereits aus Version 7.0 gestrichen, dazu gehören beispielsweise:
Hosting mehrerer Blazor-Anwendungen in einem Dokument [14]
eine bessere Kontrolle über die Blazor Server Circuits [15]
echtes Multi-Threading für Blazor WebAssembly (das soll nur als Preview in .NET 7.0 RTM enthalten sein) [16]
Value Converter in Entity Framework Core, die mehrere Spalten umfassen können [17]
Mapping von SQL Resultsets auf beliebige Klassen, die nicht als Entitätstypen deklariert sind [18]
In C# 11 ist das in früheren Previews eingeführte Parameter Null Checking mit doppeltem Ausrufezeichen entfallen. Wie das Language Design Meeting (LDM) am 13. April 2022 [19] entschieden hat, wurde das C#-Sprachfeature Parameter Null Checking
void Print(string s!!) { ... }
wieder aus dem C#-11-Compiler ausgebaut. Seit .NET 7.0 Preview 6 sieht der Entwickler bei !! den Compilerfehler „The 'parameter null-checking' feature is not supported.“ Nach so vielen frustrierenden Nachrichten widmet sich der Rest dieses Beitrags den Funktionen, die tatsächlich vorhanden und in .NET 7 Preview 7 bzw. seit Preview 5 hinzugekommen sind. Über die Neuerungen in Preview 1 und 2 berichtete ich schon in [11], über Preview 3 und 4 in [20].
Die Basisklasse System.IO.Stream bietet seit Preview 5 die zwei neuen Methoden ReadExactly() und ReadAtLeast() in Ergänzung zum altbekannten Read().ReadExactly() liest garantiert genau die Anzahl der angeforderten Bytes. ReadAtLeast() liest mindestens die Anzahl der angeforderten Bytes. Die Methode kann mehr lesen, wenn mehr Daten verfügbar sind, bis zur Größe des Puffers. In beiden Fällen gilt: Falls der Stream endet, bevor die angeforderten Bytes gelesen wurden, wird der Laufzeitfehler EndOfStreamException erzeugt. Bei ReadAtLeast() kann der Entwickler den Laufzeitfehler durch einen Parameter throwOnEndOfStream: false unterdrücken.
Bei den LINQ-Operatoren gibt es seit .NET 7.0 Preview 7 die Methoden Order() und OrderDescending() als Ergänzung zu OrderBy() und OrderByDescending(). Die neuen Methoden erlauben eine verkürzte Syntax zum Sortieren von Listen mit elementaren Datentypen. So kann der Entwickler nun
var datenlisteSortiert = datenliste.OrderDescending();
statt
var datenlisteSortiert = datenliste.OrderByDescending(x => x);
schreiben. Zunächst einmal ist da wieder ein Fehler im Blogeintrag (Abb. 1). Dort steht OrderByDescending() mit by und ohne Parameter, was es aber nicht gibt.
Abb. 1: Ein weiterer Codefehler im Microsoft Blog[21]
OrderDescending() ohne by und ohne Parameter klappt im Test mit LINQ-to-Objects (also IEnumerable<T>), allerdings versagt Entity Framework Core beim Versuch, dies auf eine Datenbank (also IQueryable<T>) anzuwenden. Dieser Code
var q = (from f in efcore_context.FlightSet
select f.Departure)
.OrderDescending()
.ToList();
liefert den Laufzeitfehler „The LINQ expression DbSet<Flight>() .Select(f => f.Departure).OrderDescending() could not be translated.“ Diesen Effekt gab es auch schon mit den in .NET 6.0 eingeführten LINQ-Operatoren, z. B. MinBy(), MaxBy(), DistinctBy(), Chunk()), die selbst in Preview 7 immer noch nicht in Entity Framework Core 7.0 unterstützt werden. Kürzlich wurde sogar verkündet, dass auch dieses Feature aus Entity Framework Core 7.0 kommen wird [22].
Für den in .NET 7.0 Preview 2 eingeführten Quellcodegenerator für reguläre Ausdrücke [23] gibt es nun ein Refactoring in Visual Studio, das dem Entwickler bei der Verwendung eines regulären Ausdrucks in der Klasse Regex anbietet, dies in die Syntax des Quellcodegenerators zu überführen (Abb. 2).
Abb. 2: Refactoring für reguläre Ausdrücke zum Quellcodegenerators [RegexGenerator] [24]
Allerding gibt es in .NET 7 Preview 6 und 7 an der Stelle einen Bug, das nicht in den Known Issues [25] hinterlegt ist: Die Klasse RegexGenerator generiert nun Programmcode, der nicht kompilierbar ist (Abb. 3).
Abb. 3: Der per Annotation [RegexGenerator] vom Compiler generierte Programmcode kompiliert nicht in .NET 7.0 Preview 6 und Preview 7
Die bereits seit .NET 6.0 verfügbaren (aber dort als Preview-Feature deklarierten) neuen Schnittstellen und Klassen für generische Mathematik (Generic Math) hat Microsoft weiterentwickelt. Diese Neuerungen werden in einem Blogeintrag [26] ausführlich erklärt.
Im Bereich Performance hat Microsoft zu .NET 7.0 Preview 5 dokumentiert [27], dass der Aufruf von Methoden und Properties per Reflection um den Faktor 3 bis 4 verbessert sei.
Der neuere in .NET Core 3.0 eingeführte JSON Serializer System.Text.Json bietet seit .NET 7.0 Preview 6 eine Anpassbarkeit der Serialisierung über sogenannte Type Info Resolver. So kann ein Entwickler die Serialisierung und Deserialisierung anpassen, ohne die zu betreffende .NET-Klasse verändern zu müssen. Das ist hilfreich, wenn diese Klasse nicht im Quellcode vorliegt. Der Programmcode in Listing X1 sorgt dafür, dass
bei Serialisierung- und Deserialisierung der Klasse Punkt das Property Memo ignoriert wird und
die Koordination X und Y (beide als Zahl vom Typ int deklariert) auch aus Zeichenketten wie "123" und "456" deserialisierbar sind.
Zudem gibt es in System.Text.Json seit Preview 5 Unterstützung für Polymorphismus über die neue Annotation [JsonDerivedType] [28].
Die Listings X1 bis X5 zu diesem Artikel finden Sie in unserem GitHub-Repository.
Microsoft liefert seit .NET 7.0 Preview 6 zwei neue Blazor-Projektvorlagen: Blazor Server App Empty und Blazor WebAssembly App Empty (Abb. 4).
Abb. 4: Projektvorlage „Blazor WebAssembly App Empty“
Anders als die bisherigen Projektvorlagen mit ASP.NET Core 3.x für Blazor Server und Blazor WebAssembly, enthalten die neuen Empty-Vorlagen nicht mehrere Webseiten und Bootstrap als CSS-Framework. Der Entwickler bekommt nur eine einzige Webseite mit diesem Inhalt:
@page "/"
<h1>Hello, world!</h1>
Die einzige mitgelieferte CSS-Datei /wwwroot/app.css (bei Blazor WebAssembly) bzw_. site.css_ (bei Blazor Server) enthält nur das notwendige CSS für den Informationskasten am unteren Bildschirmrand beim Auftreten eines Laufzeitfehlers (siehe #blazor-error-ui in Abbildung 4). Auch auf eine Masterpage oder Navigationsleiste verzichten die neuen Vorlagen. Die Vorlagen legt man über Visual Studio 2022 Version 17.3 oder via .NET CLI an:
dotnet new blazorserver-empty
dotnet new blazorwasm-empty
In der Standardprojektvorlage für Blazor WebAssembly (die nicht den Zusatz „Empty“ hat) gibt es eine Neuerung seit ASP.NET Core 7.0 Preview 7: Sie beinhaltet nun eine prozentuale Ladeanimation (Abb. 5). Die SVG-Animation ist über Style-Sheet-Klassen, die in der Datei /wwwroot/css/app.css liegen, anpassbar. Abbildung 5 zeigt aber einen weiteren Bug: Die Prozentanzeige 61 Prozent entspricht nicht dem Kreisanteil. In der Vorlage Blazor WebAssembly App Empty sieht man statt der Animation den Text „Loading…“.
Abb. 5: Ladeanimation für eine Blazor-WebAssembly-Anwendung in Version 7.0 Preview 7
DataGrid-Steuerelemente für Blazor gab es bisher nur von Drittanbietern (z. B. Developer Express, Syncfusion, Telerik, Infragistics, Radzen, GrapeCity/Component One) oder als Communitylösungen [29]. Erstmals seit .NET 7 Preview 6 liefert Microsoft für Blazor selbst ein DataGrid. Das neue Steuerelement steckt im NuGet-Paket Microsoft.AspNetCore.Components.QuickGrid, hat aber vorerst den Status experimentell. Mit dem Namen QuickGrid wollen die Redmonder Entwickler:innen betonen, dass man mit dem Steuerelement eine „performance baseline for anyone building Blazor data grid components“ [30] schaffen will. Eine Livedemonstration mit Blazor WebAssembly gibt es ebenfalls [31]. QuickGrid bietet aber bisher weder das Umsortieren der Spalten durch den Benutzer noch eine Verschachtelung von Zeilen und auch keine Datenbearbeitung an.
Bei der Datenbindung in allen Blazor-Varianten (also Blazor WebAssembly, Blazor Server, Blazor Desktop und Blazor MAUI) gibt es seit Preview 7 bei der Eigenschaft @bind die neuen Zusätze :get und :set, um das Auslesen von Werten und das Schreiben neuer Werte auf einfache Weise zu trennen. Dabei wird bei @bind:get ein Field oder eine Property erwartet und bei @bind:set eine passende Methode, zum Beispiel für ein Integer-Property, eine Methode mit der Signatur Action<\int>. Ein Beispiel zeigt Listing 1.
Listing 1
<input @bind:get="currentCount" @bind:set="SetCurrentCount" />
@code {
private int currentCount = 0;
public void SetCurrentCount(int v)
{
if (v > 0) currentCount = v;
else currentCount = 0;
}
}
Zudem kann der Entwickler bei @bind:after eine Methode angeben, die automatisch nach Aktualisierung des Klassenmitglieds ausgeführt werden soll, das bei @ bind (ohne Zusatz) festgelegt wurde:
<input @bind="currentCount" @bind:after="Log" />
Die Mischung von @bind:set und @bind:after...