.NET 11.0 Preview 6: Union Types machen sich in den .NET-Bibliotheken breit

Union Types, die C#-Implementierung von Discriminated Unions mit dem neuen Schlüsselwort union, hat Microsoft in .NET 11.0 Preview 3 eingeführt. Mit der neu erschienenen Preview 6 können Entwicklerinnen und Ent...

  • 7 min read
.NET 11.0 Preview 6: Union Types machen sich in den .NET-Bibliotheken breit

Union Types, die C#-Implementierung von Discriminated Unions mit dem neuen Schlüsselwort union, hat Microsoft in .NET 11.0 Preview 3 eingeführt. Mit der neu erschienenen Preview 6 können Entwicklerinnen und Entwickler Union Types nun an verschiedenen Stellen in .NET-Bibliotheken nutzen:

  • Die JSON-Bibliothek System.Text.Json erlaubt die Serialisierung und Deserialisierung von Union Types. Dies ist die Basis für die weiteren Features.
  • In ASP.NET Core Minimal Web-APIs mit JSON-Serialisierung können Union Types als Parameter und Rückgabewert zum Einsatz kommen.
  • In OpenAPI-Dokumenten kommt bei Union Types der Schemaeintrag anyOf zum Einsatz (siehe Abbildung). Community-Bibliotheken für OpenAPI wie Swashbuckle und NSwag können damit aber noch nicht umgehen.
  • Ebenso sind Union Types bei ASP.NET Core MVC und Razor Pages im Request Body und in Antworten erlaubt.
  • Bei ASP.NET Core SignalR dürfen Union Types in Hubs als Parameter, Rückgabewerte sowie in Streams zum Einsatz kommen, sofern als Protokoll JSON verwendet wird.
  • Im Webfrontendframework Blazor dürfen Entwicklerinnen und Entwickler Union Types als Komponentenparameter und im Persistent Component State sowie bei der Interoperabilität zwischen C# und JavaScript als Aufrufparameter und Rückgabetyp einsetzen.
Dr. Holger Schwichtenberg

Dr. Holger Schwichtenberg hat Fachbücher zu .NET 10.0, C# 14.0, Blazor 10.0 und Entity Framework Core 10.0 veröffentlicht. Er arbeitet als Berater und Trainer bei www.IT-Visions.de.

Screenshot

Der Union Type union PersonOperationResult(Person?, string, int?, Exception) in einem OpenAPI-Dokument (Abb. 1)

betterCode() .NET 11.0

(Bild: King / stock.adobe.com)

Das ist neu in .NET 11.0: Dr. Holger Schwichtenberg und weitere Experten präsentieren am 17. November 2026 auf der Online-Konferenz betterCode() .NET 11.0 die Änderungen für Entwicklerinnen und Entwickler. Tickets zum Frühbucherpreis sind im Online-Shop verfügbar.

Indexer in Erweiterungsblöcken

Die in C# 14.0 eingeführten Erweiterungsblöcke mit dem Schlüsselwort extension für bestehende Klassen, die bisher nur Methoden, Properties und Operatoren ergänzen konnten, können nun in C# 15.0 auch Indexer zu bestehenden Klassen hinzufügen.

Das folgende Listing zeigt einen Erweiterungsblock in der Klasse ListExtensions, die zum generischen Typ List<T> einen Indexer hinzufügt, der Zeichenketten als Parameter entgegennimmt. Im Standard gibt es dort nur einen Indexer für Ganzzahlen.

using System;
using System.Collections.Generic;
using System.Text;
namespace NET11_Console.C_;
extension<T>(List<T> list)
{
public T this[string index]
{
get
{
// Prüfe, ob der Index ein gültiger Integer ist
if (!Int32.TryParse(index, out int i)) return default(T);
// Prüfe, ob der Index innerhalb der Grenzen der Liste liegt
if (i > list.Count - 1) return default(T);
// liefere den Wert aus der Liste
return list[i];
}
}
}
}

Listing 1: Extension für List<T>, die einen Indexer mit einem Zeichenkettenparameter ergänzt

So verwendet man den neuen Indexer:

List<string> Prio = ["A", "B", "C"];
Console.WriteLine(Prio["1"]);   // B
Console.WriteLine(Prio["42"]);  // NULL
Console.WriteLine(Prio["unsinn"] == null);  // true

Asynchrone Validierung in der Klasse Validator

Microsoft hatte bereits in .NET 11.0 Preview 5 asynchrone Validierung in Blazor eingebaut. Nun in Preview 6 lässt sich die asynchrone Validierung auch außerhalb von Blazor in anderen Anwendungsarten nutzen. Dazu hat Microsoft die Klasse System.ComponentModel.DataAnnotations.Validator um die vier Methoden ValidateObjectAsync(), TryValidateObjectAsync(), ValidatePropertyAsync() und ValidateValueAsync() erweitert. Zuvor gab es nur Pendants ohne Async im Namen.

Zudem gibt es für asynchrone Validierungsregeln eine neue Basisklasse AsyncValidationAttribute in Ergänzung zur bestehenden Basisklasse ValidationAttribute. Das Listing zeigt einen aussagekräftigen Ausschnitt aus einem Beispiel:

using System.ComponentModel.DataAnnotations;
namespace NET11_Console.FCL;
/// <summary>
/// Datenklasse
/// </summary>
public class Rechnung
{
[Required]
public int ID { get; set; }
[Range(0.01, double.MaxValue, ErrorMessage = "Betrag muss größer als 0 sein.")]
public double Betrag { get; set; }
[Required]
[StringLength(14, MinimumLength = 8)]
[RegularExpression(@"^[A-Z]{2}[A-Z0-9]+$")]
[ValidVatNumber] // asynchrone Validierungsregel!
public string SteuerNummer { get; set; } = "";
}
/// <summary>
/// Asynchrone Validierungsregel
/// </summary>
public sealed class ValidVatNumberAttribute : AsyncValidationAttribute
{
// Synchronous IsValid. This attribute validates asynchronously only.
protected override ValidationResult? IsValid(object? value, ValidationContext context) =>
throw new InvalidOperationException("Validate this attribute with IsValidAsync.");
protected override async Task<ValidationResult?> IsValidAsync(
object? value, ValidationContext context, CancellationToken cancellationToken)
{
var ok = await new TaxBL().ValidateVatID(value.ToString());
if (ok)
{
return ValidationResult.Success;
}
else
{
return new ValidationResult("Unbekannte Steuernummer.");
}
}
}
public class FCL11_AsyncValidation
{
public async Task Run()
{
CUI.Demo(nameof(FCL11_AsyncValidation));
var re = new Rechnung() { ID = 123, SteuerNummer = "DE12345678", Betrag = 0.0d };
var context = new ValidationContext(re);
var results = new List<ValidationResult>();
// Asynchrone Validierung
await Validator.TryValidateObjectAsync(re, context, results, validateAllProperties: true);
if (results.Count > 0)
{
CUI.Error("Gefundene Fehler: " + results.Count);
foreach (var result in results)
{
CUI.LI(result.ErrorMessage, ConsoleColor.Red);
}
}
else
{
CUI.Success("Rechnungsobjekt ist einwandfrei!");
}
}
}

Listing 2: Asynchrone Validierung

Das Listing gibt aus:

Gefundene Fehler: 2

  • Betrag muss größer als 0 sein.
  • Unbekannte Steuernummer.

Asynchrone Validierung in Minimal Web-APIs

Auch in ASP.NET Core Minimal Web-APIs ist nun asynchrone Validierung möglich. Dazu muss man, wie in .NET 10.0 eingeführt, im Startcode builder.Services.AddValidation() aufrufen. Danach können Entwicklerinnen und Entwickler Objekte mit asynchronen Validatoren verwenden, zum Beispiel die oben schon verwendete Klasse Rechnung:

app.MapPost("/CheckInvoice", (Rechnung re) =>
{
new TaxBL().Process(re);
return Results.Ok(re);
});

Dieser HTTP-Request

POST {{NET10_WebAPIMinimal_HostAddress}}/CheckInvoice
Content-Type: application/json
{
"ID": 123,
"SteuerNummer": "DE12345678"
}

führt dann zur Antwort:

{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Betrag": [
"Betrag muss größer als 0 sein."
],
"SteuerNummer": [
"Unbekannte Steuernummer."
]
}
}

Wenn der Aufruf AddValidation() vergessen wird, findet keine Validierung statt und es gibt für den Aufrufer auch keinen Laufzeitfehler, der auf die fehlende Validierung hinweist.

In den Release Notes werden die klassischen Controller-basierten Web-APIs nicht erwähnt. Ein Schnelltest ergab, dass dort in Preview 6 die Übergabe eines Objekts mit asynchroner Validierung zu einem Laufzeitfehler führt: „System.InvalidOperationException: Validate this attribute with IsValidAsync.“ Ebenso wenig funktionieren bislang Union Types in Operationen in Controller-basierten Web-APIs.

Einen Kurzschluss deklarieren

Seit .NET 8.0 können Entwicklerinnen und Entwickler mit ShortCircuit() beziehungsweise MapShortCircuit() bereits bei Web-APIs deklarieren, dass ein Endpunkt zur Leistungsoptimierung ohne Verwendung aller genutzten Middleware-Komponenten (etwa Authentifizierung, CORS, Protokollierung, Komprimierung oder Durchsatzbegrenzung) direkt aufgerufen werden soll. In .NET 11.0 kann man dies nun auch deklarativ mit der neuen Annotation [ShortCircuit] erreichen.

Das geht in Minimal Web-APIs

app.MapGet("/Info",
[ShortCircuit] () => "ASP.NET Core WebAPI Controller in .NET " +
System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);

und ebenso in Controller-basierten Web-APIs:

public class PersonController : ControllerBase
{
[HttpGet]
[Route("/Info")]
[ShortCircuit]
public IActionResult Get()
{
return Content("ASP.NET Core WebAPI Controller in .NET " + System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription, "text/plain");
}
}

OpenAPI Specification 3.2 ist nun der Standard

ASP.NET-Core-basierte Web-APIs unterstützen seit .NET 11.0 Preview 2 die OpenAPI Specification in der Version 3.2. In .NET 10.0 war es Version 3.1. Vor Preview 6 war Version 3.2 nicht im Standard aktiv, sondern musste im Startcode aktiviert werden:

builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_2;
});

Am 16.6.2026 hat Microsoft bekanntgegeben, dass ab Preview 6 die Version 3.2 der Standard ist.

Eine Rückstufung auf Version 3.1 ist möglich:

builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1;
});

Abbrüche bei SignalR

Bei ASP.NET Core SignalR können Clients nun eine Verbindung abbrechen, auch wenn diese kein Streaming verwendet:

public class WorkHub : Hub
{
public async Task NameDerOperation(CancellationToken cancellationToken)
{
await Task.Delay(TimeSpan.FromMinutes(5), cancellationToken);
}
}
…
using var cts = new CancellationTokenSource();
var ergebnis = connection.InvokeAsync("NameDerOperation", cts.Token);
…
cts.Cancel();
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Betrag": [
"Betrag muss größer als 0 sein."
],
"SteuerNummer": [
"Unbekannte Steuernummer."
]
}
}

Prozesse ausgesetzt starten

Microsoft hat bereits in Preview 4 und Preview 5 Erweiterungen für die Prozessverwaltung gebracht. Auch Preview 6 bietet hier Neuerungen: Entwicklerinnen und Entwickler können Prozesse nun direkt im Zustand „Suspended“ starten. Dafür gibt es in der Klasse ProcessStartInfo die neue Boolean-Eigenschaft StartSuspended und in der Klasse SafeProcessHandle die Methode Resume(), siehe Listing.

public async Task StartSuspended()
{
CUI.Demo(nameof(StartSuspended));
var startInfo = new ProcessStartInfo("notepad.exe") { StartSuspended = true };
using var process = Process.Start(startInfo)!;
CUI.Print(DateTime.Now.ToLongTimeString() + ": Neuer Prozess mit ID #" + process.Id + " ist gestartet!");
CUI.Print(IsProcessSuspended(process) ? "Prozess ist ausgesetzt." : "Prozess läuft!");
await Task.Delay(2000);
process.SafeHandle.Resume();
CUI.Print(IsProcessSuspended(process) ? "Prozess ist ausgesetzt." : "Prozess läuft!");
}

Listing 3: Einen Prozess ausgesetzt starten

Die Klasse SafeProcessHandle bietet außerdem die neuen Methoden Open() und TryOpen(), um einen Prozess anhand der ID direkt anzusprechen. In der Klasse Process gibt es die neue Methode TryGetProcessById(), die im Gegensatz zur bestehenden ProcessById() den Wert false liefert, anstatt eine Ausnahme auszulösen, wenn kein Prozess mit der angegebenen ID existiert.