First preview version of .NET 10.0 extends the class libraries

The first preview version of the tenth .NET version contains a number of innovations in the class libraries as well as new language features in C# 14.0.

listen Print view

(Image: Pincasso/Shutterstock.com)

13 min. read
By
  • Dr. Holger Schwichtenberg
Contents

Two months after the release of .NET 9.0, Microsoft is starting the preview series for .NET 10.0. The first preview version is available in the .NET download area at Microsoft (see Figure 1).

Dr. Holger Schwichtenberg
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.

This version will not be installed with the Visual Studio 2022 updates that are released at the same time (see Figure 2).

The .NET 10.0 installation screen reports successful setup (Fig. 1).

(Image: Screenshot (Holger Schwichtenberg))

The current update to Visual Studio does not automatically install the first preview of .NET 10.0 (Fig. 2).

(Image: Screenshot (Holger Schwichtenberg))

Compile applications with .NET 10.0 Preview 1, but in both Visual Studio 2022 17.13.1 and Visual Studio 2022 17.14.0 Preview 1.1. However, .NET 10.0 is not available for selection when creating a project in Visual Studio 2022 17.13.1. Here you have to set <TargetFramework>net10.0</TargetFramework> manually in the project. Visual Studio 2022 17.14.0 Preview 1.1 already knows .NET 10.0.

As in the last two .NET versions, Microsoft is again providing new operators for Language Integrated Query (LINQ). This time, LeftJoin() and RightJoin() are two elementary operators from set theory and relational databases. In fact, these operations were already possible in LINQ, but with the help of a grouping and DefaultIfEmpty(). The new methods LeftJoin() and RightJoin() simplify their use:

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 -"));
}

The new operators LeftJoin() and RightJoin() are also supported in Entity Framework Core 10.0 for database accesses, see the issues “Support the new .NET 10 LeftJoin operators” and”Fully support right outer joins”.

The new generic class System.Collections.Generic.OrderedDictionary<T,T> introduced in .NET 9.0 already offered a method TryAdd(), which attempts to add an element. In addition to the existing variant TryAdd(TKey key, TValue value), there is now also TryAdd(TKey key, TValue value, out int index). This new overload returns the index if the element already exists in the set. Similarly, TryGetValue() is now available with an overload that not only returns the value of an entry, but also the position by 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}.");
}

To check IP addresses, there is now the static method IPAddress.TryParse(), which retrieves an IP address from a character string or

the types ReadOnlySpan<char> or ReadOnlySpan<byte>. The return value in bool and the extracted IP address is delivered in the form of an instance of the IPAddress class as a out parameter. If you only want to check whether the IP address is correct, write IPAddress.TryParse(input, out _).

In .NET 10.0, Microsoft offers another variant with less internal effort in the static method IsValid(), for example System.Net.IPAddress.IsValid("192.168.1.0"). Microsoft now simply turns a previously internal method TargetHostNameHelper.IsValidAddress() outwards.

In the JSON library, Microsoft has extended the JsonElement class with the GetPropertyCount() method, which can be used to determine how many properties a JSON object has:

JsonElement element1 = JsonSerializer.Deserialize<JsonElement>("""{ "ID" : 1, "Name" : "Dr. Holger Schwichtenberg", "Website": "www.IT-Visions.de" }""");
Console.WriteLine(element1.GetPropertyCount()); // 3

Previously, it was only possible to determine the number of objects in an array:

JsonElement element2 = JsonSerializer.Deserialize<JsonElement>("""[ 1, 2, 3, 4 ]""");
Console.WriteLine(element2.GetArrayLength()); // 4

In the JsonArray class in the System.Text.Json.Nodes namespace, Microsoft offers the two new methods RemoveRange() and RemoveAll(). RemoveRange() accepts two numbers for index and count. The index is zero-based as usual: array.RemoveRange(5, 2) removes the sixth and seventh elements. RemoveAll() expects a Predicate<JsonNode> object and allows the specification of a deletion criterion. For example 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);

Microsoft has also implemented the following in .NET 10.0 Preview 1:

  • In the class X509Certificate2Collection, the new method FindByThumbprint() exists to find a certificate based on the SHA fingerprint
  • The ISOWeek class offers three new methods for handling the DateOnly data type. Previously, only System.DateTime was possible here:
  • The implementation of the processing of ZIP archives has been optimized. Among other things, according to Microsoft, the updating of ZIP archives has been accelerated by 99.8 % (adding a 2 GB file from 177.7 ms to 0.13 ms) and now uses 99.99 % less RAM (now 7.01 KB instead of 2 GB RAM).
  • The annotation [JsonSourceGenerationOptions] for the source generator in the library Text.Json now also allows setting the behavior for circular references to Unspecified, Preserve or IgnoreCycles
  • In Windows Forms, some overloads of the GetData() method for the clipboard and drag-and-drop operations that still use the BinaryFormatter, which Microsoft has expanded in .NET 9.0 and for which a separate NuGet package is now required, are now marked as “obsolete”. Instead, there are now new operations that work with JSON, including SetDataAsJson() and TryGetData().

According to the release notes, there are only quality improvementsin WPF. There are new settings in .NET MAUI 10.0. In addition, Android projects can now be started directly from the command line with dotnet run. In the .NET SDK there is an optimization to speed up the package restore.

Don't miss any news – follow us on Facebook, LinkedIn or Mastodon.

This article was originally published in German. It was translated with technical assistance and editorially reviewed before publication.