.NET 10.0 Preview 3 bringt Typ-Erweiterungen
Das nachträgliche Erweitern von .NET-Typen um mehr als Instanzmethoden ist schon lange in Arbeit. C# 14.0 erlaubt das nun für Properties und statische Methoden.

(Bild: Pincasso/Shutterstock.com)
- Dr. Holger Schwichtenberg
.NET 10.0 Preview 3 steht seit gestern zum Download auf der .NET-Downloadseite bereit bereit. Als Entwicklungsumgebung braucht man dazu wie bisher Visual Studio 17.14 Preview 2.0. Dazu gab es gestern kein Update, was dazu fĂĽhrt, dass man die neuen Sprachfeatures aus .NET 10.0 Preview 3 in Visual Studio noch nicht ĂĽbersetzen kann, da Visual Studio laut C# Language Feature Status diese erst ab Preview 3.0 kennen wird. Beim Bearbeiten in Visual Studio Code und bei der Ăśbersetzung per Kommandozeilenbefehl dotnet build
funktionieren die neuen Features aber bereits.
(Bild: Screenshot (Holger Schwichtenberg))
Extensions-Blöcke in C# 14.0
Die nachträgliche Erweiterbarkeit von Klassen, auch wenn diese bereits andernorts kompiliert sind (zum Beispiel in den von Microsoft gelieferten Klassen in den .NET-Klassenbibliotheken), um zusätzliche Methoden gibt es unter dem Namen "Extension Methods" seit C#-Sprachversion 3.0. Diese erschien zusammen mit .NET Framework 3.5 im Jahr 2007. Man konnte dabei aber nur Instanzmethoden ergänzen. So musste man zwangsweise Konstrukte, die vom Namen her Properties waren, leidigerweise als Methoden ausdrücken, siehe IsEmptyClassic()
in Listing 1:
public static class StringExtensionClassic
{
public static string TruncateClassic(this string s, int count)
{
if (s == null) return "";
if (s.Length <= count) return s;
return s.Substring(0, count) + "...";
}
public static bool IsEmptyClassic(this string s)
=> String.IsNullOrEmpty(s);
}
Listing 1: Klassische Extension Methods
In C# 14.0 gibt es nun mit dem neuen Block-SchlĂĽsselwort extension
eine verallgemeinerte Möglichkeit der Erweiterung bestehender .NET-Klassen. Das Schlüsselwort extension
muss Teil einer statischen, nicht-generischen Klasse auf der obersten Ebene sein (also keine Nested Class). Nach dem SchlĂĽsselwort extension
deklariert man den zu erweiternden Typ, in Listing 2 die Klasse System.String
(alternativ abgekĂĽrzt durch den eingebauten Typ string
). Alle Methoden und Properties innerhalb des Extension-Blocks erweitern dann den hier genannten Typen. Aktuell kann man in diesen Extension-Blöcken folgende Konstrukte verwenden (siehe Listing 2):
- Instanz-Methoden
- Statische Methoden
- Instanz-Properties mit Getter
- Statische Properties mit Getter
Beim Versuch, einer Property einen Setter zu geben, meckert der Compiler leider mit der unzutreffenden Meldung "Extension declarations can include only methods or properties".
public static class StringExtension
{
extension(System.String s)
{
// Erweitern um eine Instanz-Methode
public string Truncate(int count)
{
if (s == null) return "";
if (s.Length <= count) return s;
return s.Substring(0, count) + string.Dots;
}
// Erweitern um eine Instanz-Eigenschaft
public bool IsEmpty => String.IsNullOrEmpty(s);
// Erweitern um eine statische Methode
public static string Create(int count, char c = '.')
{
return new string(c, count);
}
// Erweitern um eine statische Instanz-Eigenschaft
public static string Dots => "...";
}
}
Listing 2: Extension fĂĽr System.String mit C# 14.0
Listing 3 zeigt den Aufruf der alten und neuen Erweiterungen. Entwicklerinnen und Entwickler haben nun die Wahl, für Extension Methods die alte oder die neue Syntax zu verwenden. Visual Studio wird Refactoring-Methoden zur Umwandlung zwischen beiden Syntaxformen anbieten. Zudem plant Microsoft in kommenden Preview-Versionen weitere Konstrukte in Extension-Blöcken zu erlauben, etwa Konstruktoren. Die Syntax für Extensions, die Microsoft für C# 13.0 in Arbeit hatte (public implicit extension Name for Typ
) wurde verworfen.
public class StringExtensionDemo
{
public void Run()
{
string s1old = "Hello World";
string s1oldtruncated = s1old.TruncateClassic(5);
Console.WriteLine(s1oldtruncated); // Hello...
string s2old = null;
Console.WriteLine(s2old.IsEmptyClassic()); // true
string s1 = "Hello World";
string s1truncated = s1.Truncate(5);
Console.WriteLine(s1truncated); // Hello...
string s2 = null;
Console.WriteLine(s2.IsEmpty); // true
string s3 = string.Create(5, '#');
Console.WriteLine(s3); // "#####"
}
}
Listing 3: Aufruf der Erweiterungen in Listing 1 und 2
Null-Conditional Assignment in C# 14.0
Ein weiteres sehr hilfreiches neues Sprachkonstrukt in C# 14.0 nennt Microsoft Null-Conditional Assignment. Damit können Entwicklerinnen und Entwickler eine Zuweisung an eine Eigenschaft machen, ohne vorher zu prüfen, ob das Objekt null
ist. Anstelle von
Website aktuelleDOTNETWebsite = Website.Load(123);
if (aktuelleDOTNETWebsite!= null)
{
// Aktualisieren der URL
aktuelleDOTNETWebsite.Url = "https://www.dotnet10.de";
}
darf man nun verkĂĽrzt mit dem Fragezeichen schreiben:
aktuelleDOTNETWebsite?.Url = "https://www.dotnet10.de";
Dies fĂĽhrt zur Laufzeit zu keinem Fehler. Allerdings passiert auch rein gar nichts, falls die Variable aktuelleDOTNETWebsite
den Wert null
besitzt.
Verbesserungen fĂĽr OpenAPI Specification (OAS)
In der Projektvorlage "ASP.NET Core Web API (native AOT)" ist nun die Metadatengenerierung mit OpenAPI Specification (OAS) im Standard aktiviert. Mit dem Befehl
dotnet new webapiaot --name ITVisionsWebAPI
entsteht ein Projekt, das das NuGet-Paket Microsoft.AspNetCore.OpenApi referenziert und die OpenAPI-UnterstĂĽtzung in Program.cs mit
builder.Services.AddOpenApi();
und
app.MapOpenApi();
aktiviert.
Bisher war die OpenAPI-Unterstützung nur in der Projektvorlage "webapi" aktiv, die keine Kompilierung mit dem NativeAOT-Compiler erlaubt. In der Projektvorlage "webapiaot" können Entwicklerinnen und Entwickler die OpenAPI-Features wahlweise auch deaktivieren, mit dem Parameter --no-openapi
:
dotnet new webapiaot --name ITVisionsWebAPI --no-openapi
Das in Preview 2 vorhandene Problem, dass WebAPI-Projekte immer einen 404-Fehler lieferten, ist in Preview 3 behoben. Die XML-Kommentarinformationen <summary>
, <remark>
und <param>
, die man seit Preview 2 in das OpenAPI-Dokument ĂĽbernehmen kann, sind dort sichtbar in summary
und description
.
Validierung fĂĽr ASP.NET Core Minimal WebAPIs
ASP.NET Core Minimal WebAPIs beherrschen nun auch die Parametervalidierung mit Data Annotations. Das konnten bisher nur die Controller-basierten WebAPIs.
FĂĽr Minimal WebAPIs mĂĽssen Entwicklerinnen und Entwickler das Validierungsfeature allerdings erst in der Projektdatei aktivieren
<PropertyGroup>
<!-- Enable the generation of interceptors for the validation attributes -->
<InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.Http.Validation.Generated</InterceptorsNamespaces>
</PropertyGroup>
ebenso wie in der Startdatei:
builder.Services.AddValidation();
Dann kann man mit den bekannten Validierungsannotationen wie [MinLength]
, [MaxLength]
, [Range]
, [Url]
, [EmailAddress]
, [Phone]
, [CreditCard]
, [RegularExpression]
und vielen mehr (siehe Liste in Microsoft Learn) auch in Minimal WebAPIs arbeiten, zum Beispiel:
app.MapGet("/checkwebsite", ([MinLength(5)] string name, [Url] string url)
=> WebsiteChecker.CheckWebsite(name, url));
Hier fĂĽhrt nun der HTTP-Aufruf
http://localhost:5245/checkwebsite?name=ITVisions&url=www.IT-Visions.de
zur Laufzeit zur RĂĽckgabe eines JSON-Dokuments mit zwei Validierungsfehlern (siehe Abbildung 2).
(Bild: Holger Schwichtenberg)
Deklarativer Persistent Component State in Blazor 10.0
Microsofts Webframework ASP.NET Core erlaubt bei Blazor Server und Blazor WebAssembly bisher schon die Übergabe von Daten zwischen dem Prerendering und dem Hauptrendering via JSON-Anhang in HTML-Dokumenten. Dieser Persistent Component State war bisher aber recht aufwendig für Entwicklerinnen und Entwickler in der Realisierung, denn man musste zunächst ein Objekt per Dependency Injection bekommen
@inject PersistentComponentState ApplicationState
und dann den Zustand als Objekt dort ablegen
private PersistingComponentStateSubscription? persistingSubscription;
persistingSubscription = ApplicationState.RegisterOnPersisting(() =>
{
ApplicationState.PersistAsJson("name", daten);
return Task.CompletedTask;
});
und wieder explizit herausholen
if (ApplicationState.TryTakeFromJson<Typ>("name", out var daten))
{
Daten = daten;
}
Dies hat Microsoft in .NET 10.0 Preview 3 radikal vereinfacht, mit dem deklarativen Persistent Component State. Entwicklerinnen und Entwickler mĂĽssen jetzt nur noch eine Property mit der Annotation [SupplyParameterFromPersistentComponentState]
versehen und sich sonst um nichts kĂĽmmern:
[SupplyParameterFromPersistentComponentState]
public Daten? daten { get; set; }
Verbesserungen fĂĽr Blazor WebAssembly
Das in Blazor 9.0 eingeführte Fingerprinting funktioniert nun auch für die in Blazor eingebaute JavaScript-Datei in den sogenannten "Standalone Blazor WebAssembly"-Projekten, die nicht in ASP.NET Core betrieben werden, sondern auf einem statischen Webserver gehostet sein können. Dazu verwendet man in den Projekteinstellungen
<WriteImportMapToHtml>true</WriteImportMapToHtml>
und im HTML-Kopfbereich
<script type="importmap"></script>
Das Script-Tag ist dann
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
Diese Verbesserung ist in der Projektvorlage aber noch nicht so umgesetzt, was man sieht, wenn man ein neues Projekt anlegt:
dotnet new blazorwasm -n ITVisionsDemo
FĂĽr Blazor WebAssembly musste man die Umgebungsdefinition (Development, Staging, Production) bisher per HTTP-Header vornehmen. Nun nimmt Microsoft beim Kompilieren von Blazor-WebAssembly-Anwendungen automatisch folgende Umgebungen an:
- Bei
dotnet build
: "Development" - Bei
dotnet publish
: "Production"
Entwicklerinnen und Entwickler können die Umgebungsart auch per Projekteinstellung explizit setzen, etwa für Staging:
<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>
In Blazor WebAssembly ist in der Klasse HttpClient
im Standard nun das Response-Streaming aktiv, um die Leistung zu erhöhen und den Speicherbedarf zu verringen. Allerdings sind dadurch nur noch asynchrone Operationen möglich. Dies gehört zu den Breaking Changes in .NET 10.0, die in Microsoft Learn dokumentiert sind. Das Response-Streaming im HttpClient ist deaktivierbar mit:
requestMessage.SetBrowserResponseStreamingEnabled(false);
Verbesserungen in der .NET-Basisklassenbibliothek
Eine Verbesserung für den Zertifikatsexport stand schon in den Release Notes zu .NET 10.0 Preview 2, funktionierte dort aber nicht. Nun in Preview 3 kann man tatsächlich Zertifikate mit AES-Verschlüsselung und Hashing via SHA-2-256 exportieren, mit der neuen Methode ExportPkcs12()
als Ergänzung zur bestehenden Methode Export()
:
byte[] pfxData = cert.ExportPkcs12(Pkcs12ExportPbeParameters.Pbes2Aes256Sha256, password);
Die alten Verfahren (3DES-VerschĂĽsselung und SHA1-Hashing) aus Export()
sind aber auch über die neue Methode möglich:
byte[] pfxData = cert.ExportPkcs12(Pkcs12ExportPbeParameters.Pkcs12TripleDesSha1, password);
DarĂĽber hinaus bietet .NET 10.0 Preview 3 einige kleinere Verbesserungen fĂĽr Validation Context, Telemetry und ML.NET, die man in den Release Notes findet.
Ausblick
Bis zum Erscheinen im November 2025 sind noch vier weitere Preview-Versionen und zwei Release-Candidate-Versionen von .NET 10.0 zu erwarten.
(mai)