.NET 8.0: Zweite Preview beschleunigt WebAssembly-Anwendungen

Neben dem Jiterpreter für Blazor WebAssembly, der einen Just-in-Time-Compiler mit einem Interpreter kombiniert, bringt .NET 8 Preview 2 neue Annotationen.

In Pocket speichern vorlesen Druckansicht 4 Kommentare lesen

(Bild: Shutterstock)

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

Microsoft hat in den letzten Versionen des modernen .NET immer wieder Verbesserungen in den Basisklassen implementiert, die zum Teil zwei Jahrzehnte alt sind. In .NET 8.0 Preview 2 erweitert Microsoft die Annotationsklassen im Namensraum System.ComponentModel.DataAnnotations.

Die neue Annotation [Length] kann verwendet werden, um die Mindest- und Maximallänge von Objektmengen und Zeichenketten zu setzen. Mit [AllowedValues] und [DeniedValues] kann man eine Liste erlaubter Werte für Properties eines Objekts angeben. [Base64String] prüft, ob eine Zeichenkette Base64-codiert ist.

Zudem hat Microsoft bestehende Annotationen erweitert. [Required] hat bisher nur auf Null-Werte sowie im Zusammenspiel mit Strings auf leere Zeichenketten oder solche gepürft, die nur aus Leerzeichen bestehen. Mit der nun zusätzlichen Eigenschaft DisallowAllDefaultValues lassen sich auch anderen Fälle erfassen, die einen Standardwert darstellen. So führt [Required(DisallowAllDefaultValues = true)] zu einem Validierungsfehler auf einer GUID, wenn diese den Wert Guid.Empty (00000000-0000-0000-0000-000000000000) hat. Beim Datentyp DateTime wird der 01.01.0001 damit nicht mehr als gültiges Datum akzeptiert. Die [Range]-Annotation besitzt nun die zusätzlichen Eigenschaften MinimumIsExclusive und MaximumIsExclusive, um die genannte Unter- und Obergrenze selbst auszuschließen.

Folgender Code zeigt die neuen Annotationen:

using System.ComponentModel.DataAnnotations;
using ITVisions;
 
namespace NET8Konsole;
 
public class Person
{
 
// 00000000-0000-0000-0000-000000000000 verboten:
 [Required(DisallowAllDefaultValues = true)] 

 public Guid PersonGuid { get; set; } 
 
// 01.01.0001 verboten:
 [Required(DisallowAllDefaultValues = true)] 
 public DateTime Geburtsdatum { get; set; } 
 
  // Alter >0 und < 120 :
 [Range(0d, 120d, MinimumIsExclusive = true, 
        MaximumIsExclusive = true)] 
 public double Alter { get; set; } 
 
 // nur diese Anreden erlaubt:
 [AllowedValues("", "Dr.", "Prof. Dr.")] 
 public string? Anrede { get; set; }
 
 [Length(2, 50)] // Name mindestens 2, maximal 50 Zeichen
 public string? Name { get; set; }
 
 [Length(0, 3)] // bis zu 3 Websites
 public List<string> Websites { get; set; } = new();
 
 // muss Base64 sein. Das ist natuerlich kein Schutz, 
 // sondern nur ein Beispiel 
 [Base64String] 
 public string? Kennwort { get; set; }
}
 
public class NET8_Annotations_Demo
{
 
 public void Run()
 {
  var hs = new Person() 
    { Name = "Holger Schwichtenberg", 
      Anrede = "Dr", Kennwort = "geheim" };
  hs.Websites.Add("www.IT-Visions.de");
  hs.Websites.Add("www.dotnet-doktor.de");
  hs.Websites.Add("www.entwickler-lexikon.de");
  hs.Websites.Add("www.dotnet7.de");
  hs.Websites.Add("www.dotnet8.de");
 
  // Validierung durchführen
  var ctx = new ValidationContext(hs);
  var results = 
    new System.Collections.Generic.List<ValidationResult>();
 
  if (!Validator.TryValidateObject(hs, ctx, results, true))
  {
   // Fehler ausgeben
   foreach (var validationResult in results)
   {
    CUI.Warning(validationResult.ErrorMessage);
   }
  }
  else
  {
   // Es war alles OK
   CUI.Success("Validation successful!");
  }
 
 }
}

Beim Ausführen des Programmcodes mit .NET 8.0 Preview 2 kommt es zu diesen sechs Validierungsfehlern:

The PersonGuid field is required.

The Geburtsdatum field is required.

The field Alter must be between 0 exclusive and 120 exclusive.

The Anrede field does not equal any of the values specified \
in AllowedValuesAttribute.

The field Websites must be a string or collection type \
with a minimum length of '0' and maximum length of '3'.

The Kennwort field is not a valid Base64 encoding.

Microsoft benennt im Blogeintrag zu .NET 8.0 die neuen Eigenschaften anders:

[Range(0d, 1d, IsLowerBoundExclusive = false, 
       IsUpperBoundExclusive = false)]
public double Sample { get; set; }

Die darin erwähnten Eigenschaften IsLowerBoundExclusive und IsUpperBoundExclusive findet der Compiler aber in .NET 8.0 Preview 2 nicht, wie ein Schnelltest des Autors ergab.

Ebenfalls nicht im Blogeintrag erwähnt ist eine in .NET 8.0 Preview 2 implementierte Neuerung für die Sprachsyntax von C#, die sich Primärkonstruktor nennt. Dieses Sprachfeature hatte Microsoft bereits 2014 für C# 6.0 in Arbeit und als Prototyp veröffentlicht, letztlich aber doch gestrichen.

Nun soll in C# 12.0 eine Klassendefinition direkt hinter dem Klassennamen eine Parameterliste erhalten und auch ohne Inhaltsbereich (also geschweifte Klammern) existieren können:

public class Person(Guid personGuid,string name);

Anders als bei den in C# 9.0 eingeführten Record-Typen erstellt der Primärkonstruktor jedoch keine öffentlichen Properties in der Klasse, sondern private Datenmitglieder. Um öffentlich auf diese Daten zuzugreifen, muss man die Konstruktorparameter also für Zuweisungen verwenden, wie bei Name und Website in dem folgenden Codebeispiel. Darin gibt es neben der Klasse Person eine zweite, abgeleitete Klasse Autor mit Primärkonstruktor. Annotation auf den Konstruktorparameter sind beim Primärkonstruktor syntaktisch möglich, der Validator von .NET interessiert sich dafür aber leider nicht: Er beachtet nur die öffentlichen Properties.

using System.ComponentModel.DataAnnotations;
using ITVisions;
 
namespace NET8Konsole.CS12;
 
/// <summary>
/// Klasse mit Primaerkonstruktor 
/// </summary>
public class Person(Guid personGuid, string name)
{
 // 00000000-0000-0000-0000-000000000000 verboten:
 [Required(DisallowAllDefaultValues = true)] 
 public Guid PersonGuid { get; set; } = personGuid;
 
 [Length(2, 50)] // Name mindestens 2, maximal 50 Zeichen
 public string Name { get; set; } = name;
 
 public Person() : this(Guid.Empty, "")
 {
  // Aufruf ohne Parameter moeglich, 
  // fuehrt aber zu einem ungueltigen Objekt 
 }
 
 public override string ToString()
 {
  return $"Person {personGuid}: {Name}";
 }
}
 
/// <summary>
/// Abgeleitete Klasse mit Primaerkonstruktor 
/// </summary>
public class 
  Autor(Guid personGuid, string name, 
        string website) : Person(personGuid, name)
{
 
 [Length(2, 50)] // Name mindestens 2, maximal 50 Zeichen
 public string Website { get; set; } = website;
 
 public override string ToString()
 {
  return $"Autor {personGuid}: {Name} -> {Website}";
 }
}
 
internal class CS12_PrimaryConstructors_Demo
{
 
 public void Run()
 {
  var p = new Person();
  Console.WriteLine(p.Name);
  Console.WriteLine(p.ToString());
 
  var a = new Autor(Guid.NewGuid(), 
                    "Dr. Holger Schwichtenberg",
                    "www.IT-Visions.de");
  Console.WriteLine(a.Name);
  Console.WriteLine(a.Website);
  Console.WriteLine(a.ToString());
 
 }
}

Um die Primärkonstruktoren einsetzen zu können, muss man nicht nur das Target Framework auf .NET 8.0, sondern zusätzlich auch das Tag <LangVersion> auf den Wert preview setzen:

<TargetFramework>net8.0</TargetFramework>
<LangVersion>preview</LangVersion>

Im Objekt-Relationalen Mapper Entity Framework Core 8.0 lassen sich die in Entity Framework Core 7.0 eingeführten JSON-Spalten auch in Verbindung mit SQLite-Datenbanken nutzen. Dabei werden Objekte auf einzelne Datenbankspalten mit JSON-Inhalt abgebildet. Abfragen mit Filtern und Sortieren sind dennoch möglich.

Microsoft SQL Server verarbeitet nun hierarchische Daten mit Hierarchy-ID Unterstützung. Der Blogeintrag zu Entity Framework Core 8.0 Preview 2 zeigt ein ausführliches Beispiel dazu.

Für Blazor WebAssembly gibt es bisher zwei Übersetzungsmodi: Ursprünglich existierte nur ein Interpreter, der zur Laufzeit fortwährend Microsoft Intermediate Language (MSIL) in WebAssembly-Bytecode übersetzt. In .NET 6.0 kam als Alternative ein Ahead-of-Timer-Compiler (AOT) hinzu, der zwar schnellere Codeausführung zur Laufzeit bietet, aber nicht nur langsam kompiliert, sondern im Vergleich zum interpretierten Code auch wesentlich größere Kompilate erzeugt. Das liegt an einerseits an der geringeren Ausdrucksfähigkeit von WebAssembly-Bytecode im Vergleich zu MSIL und andererseits daran, dass im AOT-Modus der MSIL-Code mitgeliefert wird.

In .NET 8.0 Preview 2 ist nun automatisch eine Verbesserung aktiv, die Microsoft als Jiterpreter bezeichnet, eine Wortmischung aus Just-in-Time-Compiler (JIT) und Interpreter. Er ersetzt zur Laufzeit Teile des Anwendungscodes durch WebAssembly-Bytecode. Das wirkt sich insbesondere bei interpretierten Blazor-WebAssembly-Anwendung positiv auf die Ausführungsgeschwindigkeit aus (siehe Abbildung 1). Der Blogeintrag erwähnt nicht näher quantifizierte Vorteile für den AOT-Modus. Microsoft will den Jiterpreter noch weiter ausbauen.

Abbildung: Leistungsverbesserungen bei Blazor WebAssembly durch den Jiterpreter in Blazor 8.0 Preview 2 (Abb. 1)

(Bild: Microsoft .NET Blog)

Für ASP.NET Core basierte WebAPIs gibt es nun einen Code-Analyzer, der warnt, wenn mehrere Parameter mit [FromBody] annotiert sind. Der Inhalt einer HTTP-Anfrage kann immer nur auf einen Parameter einer WebAPI-Operation abgebildet werden.

.NET 8.0 Preview 2 steht zum kostenfreien Download bereit. Die erfolgreiche Installation prüft der Kommandozeilenbefehl dotnet –version.

Die Installation von .NET 8.0 Preview 2 war erfolgreich (Abb. 2).

Man sollte zum Entwickeln mit dieser Vorabversion die Version 17.6 Preview 2 von Visual Studio 2022 verwenden. Die Neuerungen der IDE beschreibt die Meldung auf heise Developer. Diese Version von Visual Studio installiert .NET 8.0 aber noch nicht automatisch mit. Sofern es installiert ist, steht es in den Dialogen von Visual Studio zur Wahl. Alternativ kann man manuell das Taget Framework in der Projektdatei umstellen:

<TargetFramework>net8.0</TargetFramework>

.NET 8.0 soll im November 2023 erscheinen und als Long-Term-Support-Relase (LTS) Updates und Unterstützung für drei Jahre erhalten. Bis dahin soll es monatlich Vorschauversionen geben.

.NET 8.0 wird wie jede .NET-Hauptversion der letzten Jahre zahlreiche Breaking Changes umfassen, die Änderungen am Programmcode erfordern.

(rme)