.NET-10.0-Vorschau erweitert Klassenbibliotheken
Die erste Preview-Version der zehnten .NET-Version enthält eine Reihe von Neuerungen in den Klassenbibliotheken sowie neue Sprachfeatures in C# 14.0.
(Bild: Pincasso/Shutterstock.com)
- Dr. Holger Schwichtenberg
Zwei Monate nach dem Erscheinen von .NET 9.0 beginnt Microsoft die Vorschaureihe auf .NET 10.0. Die erste Preview-Version steht im .NET-Downloadbereich bei Microsoft bereit (siehe Abbildung 1).
Diese Version wird nicht mit den zeitgleich erschienenen Updates von Visual Studio 2022 (siehe Abbildung 2) installiert.
(Bild:Â Screenshot (Holger Schwichtenberg))
(Bild:Â Screenshot (Holger Schwichtenberg))
Anwendungen mit .NET 10.0 Preview 1 kompilieren, aber sowohl in Visual Studio 2022 17.13.1 als auch Visual Studio 2022 17.14.0 Preview 1.1. Beim Anlegen eines Projekts in Visual Studio 2022 17.13.1 steht .NET 10.0 allerdings nicht zur Auswahl. Hier muss man manuell im Projekt <TargetFramework>net10.0</TargetFramework> setzen. Visual Studio 2022 17.14.0 Preview 1.1 kennt hingegen .NET 10.0 bereits.
LINQ-Operatoren LeftJoin() und RightJoin()
Wie in den letzten beiden .NET-Versionen auch, liefert Microsoft wieder neue Operatoren für Language Integrated Query (LINQ). Dieses Mal sind es mit LeftJoin() und RightJoin() zwei elementare Operatoren aus der Mengenlehre und den relationalen Datenbanken. Tatsächlich waren diese Operationen bisher in LINQ schon möglich, allerdings umständlich mit Hilfe einer Gruppierung und DefaultIfEmpty(). Die neuen Methoden LeftJoin() und RightJoin() vereinfachen den Einsatz:
CUI.H2("--- LeftJoin ALT seit .NET Framework 3.5 ---");
var AllCompaniesWithWebsitesSetOld = outer
.GroupJoin(inner,
c => c.ID,
w => w.CompanyID,
(c, websites) => new { Company = c, Websites = websites })
.SelectMany(
x => x.Websites.DefaultIfEmpty(), // Falls keine Webseite existiert, wird `null` verwendet
(c, w) => new WebsiteWithCompany
{
Name = c.Company.Name,
URL = w.URL, // Falls `w` null ist, bleibt URL null
City = c.Company.City
});
foreach (var item in AllCompaniesWithWebsitesSetOld)
{
Console.WriteLine((item.Name != null ? item.Name + " " + item.City :
"- keine Firma - ") + " -> " + (item.URL ?? "- keine URL -"));
}
CUI.H2("--- LeftJoin NEU ab .NET 10.0 ---");
var AllCompaniesWithWebsitesSet = outer.LeftJoin(inner, e => e.ID, e => e.CompanyID, (c, w)
=> new WebsiteWithCompany { Name = c.Name, City = c.City, URL = w.URL });
foreach (var item in AllCompaniesWithWebsitesSet)
{
Console.WriteLine((item.Name != null ? item.Name + " " + item.City :
"- keine Firma - ") + " -> " + (item.URL ?? "- keine URL -"));
}
CUI.H2("--- RightJoin OLD seit .NET Framework 3.5 ---");
var WebsiteWithCompanySetOLD = inner
.GroupJoin(outer,
w => w.CompanyID,
c => c.ID,
(w, companies) => new { Website = w, Companies = companies })
.SelectMany(
x => x.Companies.DefaultIfEmpty(), // Falls kein Unternehmen existiert, bleibt `null`
(w, c) => new WebsiteWithCompany
{
Name = c.Name, // Falls `c` null ist, bleibt `Name` null
City = c.City, // Falls `c` null ist, bleibt `City` null
URL = w.Website.URL
});
foreach (var item in WebsiteWithCompanySetOLD)
{
Console.WriteLine((item.Name != null ? item.Name + " " + item.City :
"- keine Firma - ") + " -> " + (item.URL ?? "- keine URL -"));
}
CUI.H2("--- RightJoin NEU ab .NET 10.0 ---");
var WebsiteWithCompanySet = outer.RightJoin(inner, e => e.ID, e => e.CompanyID, (c, w)
=> new WebsiteWithCompany { Name = c.Name, City = c.City, URL = w.URL });
foreach (var item in WebsiteWithCompanySet)
{
Console.WriteLine((item.Name != null ? item.Name + " " + item.City :
"- keine Firma - ") + " -> " + (item.URL ?? "- keine URL -"));
}
// Zum Vergleich: Inner Join, den es seit .NET Framework 3.5 gibt
CUI.H2("--- InnerJoin seit .NET Framework 3.5 ---");
var CompaniesWithWebsitesSet = outer.Join(inner,
c => c.ID,
w => w.CompanyID,
(c, w) => new WebsiteWithCompany
{
Name = c.Name,
URL = w.URL,
City = c.City
});
foreach (var item in CompaniesWithWebsitesSet)
{
Console.WriteLine((item.Name != null ? item.Name + " " + item.City :
"- keine Firma - ") + " -> " + (item.URL ?? "- keine URL -"));
}
Die neuen Operatoren LeftJoin() und RightJoin() werden auch in Entity Framework Core 10.0 fĂĽr Datenbankzugriffe unterstĂĽtzt, siehe die Issues "Support the new .NET 10 LeftJoin operators" und "Fully support right outer joins".
TryAdd() mit Index-RĂĽckgabe
Die in .NET 9.0 neu eingefĂĽhrte generische Klasse System.Collections.Generic.OrderedDictionary<T,T> bot bisher schon eine Methode TryAdd(), die versucht, ein Element hinzuzufĂĽgen. Neben der bestehenden Variante TryAdd(TKey key, TValue value) gibt es nun auch TryAdd(TKey key, TValue value, out int index). Diese neue Ăśberladung liefert den Index zurĂĽck, falls es das Element in der Menge schon gibt. Analog dazu gibt es TryGetValue() nun mit einer Ăśberladung, die nicht nur den Wert eines Eintrags liefert, sondern auch die Position per Index:
OrderedDictionary<string, string> websites = new OrderedDictionary<string, string>();
websites.Add("Heise", "www.Heise.de");
websites.Add("Microsoft", "www.Microsoft.com");
websites.Add("IT-Visions", "www.IT-Visions.de");
var propertyName = "IT-Visions";
var value = "www.IT-Visions.de";
// bisher
if (!websites.TryAdd(propertyName, value))
{
int index1 = websites.IndexOf(propertyName); // Second lookup operation
CUI.Warning("Element " + value + " ist bereits vorhanden!");
}
// neu
if (!websites.TryAdd(propertyName, value, out int index))
{
CUI.Warning("Element " + value +
" ist bereits vorhanden an der Position " +
index + """!""");
}
// neu
if (websites.TryGetValue(propertyName, out string? value2, out int index2))
{
CUI.Success($"Element {value2} wurde gefunden an der Position {index2}.");
}
IP-Adressen prĂĽfen
Zur PrĂĽfung von IP-Adressen gibt es bisher die statische Methode IPAddress.TryParse(), die eine IP-Adresse aus einer Zeichenkette oder
den Typen ReadOnlySpan<char> beziehungsweise ReadOnlySpan<byte> extrahiert. Der RĂĽckgabewert in bool und die extrahierte IP-Adresse wird in Form einer Instanz der Klasse IPAddress als out-Parameter geliefert. Wenn man nur prĂĽfen will, ob die IP-Adresse stimmt, schreibt man IPAddress.TryParse(eingabe, out _).
In .NET 10.0 bietet Microsoft eine weitere Variante mit weniger internem Aufwand in der statischen Methode IsValid(), beispielsweise System.Net.IPAddress.IsValid("192.168.1.0"). Dabei kehrt Microsoft nun einfach eine bisher interne Methode TargetHostNameHelper.IsValidAddress() nach auĂźen.
GetPropertyCount() in System.Text.Json.JsonElement
In der JSON-Bibliothek hat Microsoft die Klasse JsonElement um die Methode GetPropertyCount() erweitert, mit der man ermitteln kann, wie viele Eigenschaften ein JSON-Objekt besitzt:
JsonElement element1 = JsonSerializer.Deserialize<JsonElement>("""{ "ID" : 1, "Name" : "Dr. Holger Schwichtenberg", "Website": "www.IT-Visions.de" }""");
Console.WriteLine(element1.GetPropertyCount()); // 3
Bisher war nur möglich, die Anzahl der Objekte in einem Array zu ermitteln:
JsonElement element2 = JsonSerializer.Deserialize<JsonElement>("""[ 1, 2, 3, 4 ]""");
Console.WriteLine(element2.GetArrayLength()); // 4
In der Klasse JsonArray im Namensraum System.Text.Json.Nodes bietet Microsoft die beiden neuen Methoden RemoveRange() und RemoveAll(). RemoveRange() nimmt zwei Zahlen entgegen für Index und Count. Der Index ist wie üblich nullbasiert: array.RemoveRange(5, 2) entfernt das sechste und siebte Element. RemoveAll() erwartet ein Predicate<JsonNode>-Objekt und erlaubt die Angabe eines Löschkriteriums. Beispielsweise array.RemoveAll(n => n.GetValue<int>() > 5):
System.Text.Json.Nodes.JsonArray array = JsonSerializer.Deserialize<System.Text.Json.Nodes.JsonArray>("""[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]""");
System.Console.WriteLine(array.Count); // 9
PrintJsonArray(array);
array.RemoveRange(5, 2);
PrintJsonArray(array);
array.RemoveAll(n => n.GetValue<int>() > 5);
PrintJsonArray(array);
Weitere Verbesserungen in der Klassenbibliothek
AuĂźerdem hat Microsoft Folgendes in .NET 10.0 Preview 1 implementiert:
- In der Klasse
X509Certificate2Collectionexsitiert die neue MethodeFindByThumbprint(), um ein Zertifikat anhand des SHA-Fingerabdrucks zu finden - Die Klasse
ISOWeekbietet drei neue Methoden zum Umgang mit dem DatentypDateOnly. Bisher war hier nurSystem.DateTimemöglich: - Die Implementierung der Verarbeitung von ZIP-Archiven wurde optimiert. Unter anderem wurde laut Microsoft das Aktualisieren von ZIP-Archiven um 99,8 % beschleunigt (ein Hinzufügen einer 2-GB-Datei von 177,7 ms auf 0,13 ms) und verbraucht nun 99,99 % weniger RAM (nun 7,01 KB statt 2 GB RAM).
- Die Annotation
[JsonSourceGenerationOptions]für den Source-Generator in der BibliothekText.Jsonerlaubt nun auch die Einstellung des Verhaltens bei zirkulären Referenzen aufUnspecified,PreserveoderIgnoreCycles - In Windows Forms sind nun einige Überladungen der
GetData()-Methode für die Zwischenablage sowie Drag-and-Drop-Operationen als "obsolet" markiert, die noch denBinaryFormatterverwenden, den Microsoft in .NET 9.0 ausgebaut hat und für den man nun ein eigenes NuGet-Paket benötigt. Dafür gibt es nun neue Operationen, die mit JSON arbeiten, darunterSetDataAsJson()undTryGetData().
In WPF gibt es laut Release Notes nur Qualitätsverbesserungen. In .NET MAUI 10.0 gibt es neue Einstellungen. Zudem können Android-Projekte nun mit dotnet run von der Kommandozeile direkt gestartet werden. Im .NET SDK gibt es eine Optimierung zum Beschleunigen des Package Restore.