|
| 1 | +--- |
| 2 | +title: What's new in .NET libraries for .NET 10 |
| 3 | +description: Learn about the new .NET libraries features introduced in .NET 10. |
| 4 | +titleSuffix: "" |
| 5 | +ms.date: 02/20/2025 |
| 6 | +ms.topic: whats-new |
| 7 | +ai-usage: ai-assisted |
| 8 | +--- |
| 9 | + |
| 10 | +# What's new in .NET libraries for .NET 10 |
| 11 | + |
| 12 | +This article describes new features in the .NET libraries for .NET 10. It has been updated for Preview 1. |
| 13 | + |
| 14 | +## Find certificates by thumbprints other than SHA-1 |
| 15 | + |
| 16 | +Finding certificates uniquely by thumbprint is a fairly common operation, but the <xref:System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Find(System.Security.Cryptography.X509Certificates.X509FindType,System.Object,System.Boolean)?displayProperty=nameWithType> method (for the <xref:System.Security.Cryptography.X509Certificates.X509FindType.FindByThumbprint> mode) only searches for the SHA-1 Thumbprint value. |
| 17 | + |
| 18 | +There is some risk to using the `Find` method for finding SHA-2-256 ("SHA256") and SHA-3-256 thumbprints since these hash algorithms have the same lengths. |
| 19 | + |
| 20 | +Instead, .NET 10 introduces a new method that accepts the name of the hash algorithm to use for matching. |
| 21 | + |
| 22 | +```csharp |
| 23 | +X509Certificate2Collection coll = store.Certificates.FindByThumbprint(HashAlgorithmName.SHA256, thumbprint); |
| 24 | +Debug.Assert(coll.Count < 2, "Collection has too many matches, has SHA-2 been broken?"); |
| 25 | +return coll.SingleOrDefault(); |
| 26 | +``` |
| 27 | + |
| 28 | +## Find PEM-encoded Data in ASCII/UTF-8 |
| 29 | + |
| 30 | +The PEM encoding (originally "Privacy Enhanced Mail", but now used widely outside of email) is defined for "text", which means that the <xref:System.Security.Cryptography.PemEncoding> class was designed to run on <xref:System.String> and `ReadOnlySpan<char>`. However, it's quite common (especially on Linux) to have something like a certificate written in a file that uses the ASCII (string) encoding. Historically, that meant you needed to open the file and convert the bytes to chars (or a string) before you could use `PemEncoding`. |
| 31 | + |
| 32 | +Taking advantage of the fact that PEM is only defined for 7-bit ASCII characters, and that 7-bit ASCII has a perfect overlap with single-byte UTF-8 values, you can now skip the UTF-8/ASCII-to-char conversion and read the file directly. |
| 33 | + |
| 34 | +```diff |
| 35 | +byte[] fileContents = File.ReadAllBytes(path); |
| 36 | +-char[] text = Encoding.ASCII.GetString(fileContents); |
| 37 | +-PemFields pemFields = PemEncoding.Find(text); |
| 38 | ++PemFields pemFields = PemEncoding.FindUtf8(fileContents); |
| 39 | + |
| 40 | +-byte[] contents = Base64.DecodeFromChars(text.AsSpan()[pemFields.Base64Data]); |
| 41 | ++byte[] contents = Base64.DecodeFromUtf8(fileContents.AsSpan()[pemFields.Base64Data]); |
| 42 | +``` |
| 43 | + |
| 44 | +## New method overloads in ISOWeek for DateOnly type |
| 45 | + |
| 46 | +The <xref:System.Globalization.ISOWeek> class was originally designed to work exclusively with <xref:System.DateTime>, as it was introduced before the <xref:System.DateOnly> type existed. Now that `DateOnly` is available, it makes sense for `ISOWeek` to support it as well. |
| 47 | + |
| 48 | +```csharp |
| 49 | +public static class ISOWeek |
| 50 | +{ |
| 51 | + // New overloads |
| 52 | + public static int GetWeekOfYear(DateOnly date); |
| 53 | + public static int GetYear(DateOnly date); |
| 54 | + public static DateOnly ToDateOnly(int year, int week, DayOfWeek dayOfWeek); |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +## String normalization APIs to work with span of characters |
| 59 | + |
| 60 | +Unicode string normalization has been supported for a long time, but existing APIs have only worked with the string type. This means that callers with data stored in different forms, such as character arrays or spans, must allocate a new string to use these APIs. Additionally, APIs that return a normalized string always allocate a new string to represent the normalized output. |
| 61 | + |
| 62 | +.NET 10 introduces new APIs that work with spans of characters, expanding normalization beyond string types and helping to avoid unnecessary allocations. |
| 63 | + |
| 64 | +```csharp |
| 65 | +public static class StringNormalizationExtensions |
| 66 | +{ |
| 67 | + public static int GetNormalizedLength(this ReadOnlySpan<char> source, NormalizationForm normalizationForm = NormalizationForm.FormC); |
| 68 | + public static bool IsNormalized(this ReadOnlySpan<char> source, NormalizationForm normalizationForm = NormalizationForm.FormC); |
| 69 | + public static bool TryNormalize(this ReadOnlySpan<char> source, Span<char> destination, out int charsWritten, NormalizationForm normalizationForm = NormalizationForm.FormC); |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +## Numeric ordering for string comparison |
| 74 | + |
| 75 | +Numerical string comparison is a highly requested feature for comparing strings numerically instead of lexicographically. For example, `2` is less than `10`, so `"2"` should appear before `"10"` when ordered numerically. Similarly, `"2"` and `"02"` are equal numerically. With the new `CompareOptions.NumericOrdering` <!--xref:System.Globalization.CompareOptions.NumericOrdering--> option, it's now possible to do these types of comparisons: |
| 76 | + |
| 77 | +<!-- >:::code language="csharp" source="../snippets/dotnet-10/csharp/snippets.cs" id="snippet1"::: --> |
| 78 | + |
| 79 | +```csharp |
| 80 | +StringComparer numericStringComparer = StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.NumericOrdering); |
| 81 | + |
| 82 | +Console.WriteLine(numericStringComparer.Equals("02", "2")); |
| 83 | +// Output: True |
| 84 | +
|
| 85 | +foreach (string os in new[] { "Windows 8", "Windows 10", "Windows 11" }.Order(numericStringComparer)) |
| 86 | +{ |
| 87 | + Console.WriteLine(os); |
| 88 | +} |
| 89 | + |
| 90 | +// Output: |
| 91 | +// Windows 8 |
| 92 | +// Windows 10 |
| 93 | +// Windows 11 |
| 94 | +
|
| 95 | +HashSet<string> set = new HashSet<string>(numericStringComparer) { "007" }; |
| 96 | +Console.WriteLine(set.Contains("7")); |
| 97 | +// Output: True |
| 98 | +``` |
| 99 | + |
| 100 | +Note that this option is not valid for the following index-based string operations: `IndexOf`, `LastIndexOf`, `StartsWith`, `EndsWith`, `IsPrefix`, and `IsSuffix`. |
| 101 | + |
| 102 | +## New `TimeSpan.FromMilliseconds` overload with single parameter |
| 103 | + |
| 104 | +The <xref:System.TimeSpan.FromMilliseconds(System.Int64,System.Int64)?displayProperty=nameWithType> method was introduced previously without adding an overload that takes a single parameter. |
| 105 | + |
| 106 | +Although this works since the second parameter is optional, it causes a compilation error when used in a LINQ expression like: |
| 107 | + |
| 108 | +```csharp |
| 109 | +Expression<Action> a = () => TimeSpan.FromMilliseconds(1000); |
| 110 | +``` |
| 111 | + |
| 112 | +The issue arises because LINQ expressions cannot handle optional parameters. To address this, .NET 10 introduces an overload takes a single parameter and modifying the existing method to make the second parameter mandatory: |
| 113 | + |
| 114 | +```csharp |
| 115 | +public readonly struct TimeSpan |
| 116 | +{ |
| 117 | + public static TimeSpan FromMilliseconds(long milliseconds, long microseconds); // Second parameter is no longer optional |
| 118 | + public static TimeSpan FromMilliseconds(long milliseconds); // New overload |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +## ZipArchive performance and memory improvements |
| 123 | + |
| 124 | +.NET 10 improves the performance and memory usage of <xref:System.IO.Compression.ZipArchive>. |
| 125 | + |
| 126 | +First, the way entries are written to a `ZipArchive` when in `Update` mode has been optimized. Previously, all <xref:System.IO.Compression.ZipArchiveEntry> instances were loaded into memory and rewritten, which could lead to high memory usage and performance bottlenecks. The optimization reduces memory usage and improves performance by avoiding the need to load all entries into memory. Details are provided in [dotnet/runtime #102704](https://github.com/dotnet/runtime/pull/102704#issue-2317941700). |
| 127 | + |
| 128 | +Second, the extraction of <xref:System.IO.Compression.ZipArchive> entries is now parallelized, and internal data structures are optimized for better memory usage. These improvements address issues related to performance bottlenecks and high memory usage, making `ZipArchive` more efficient and faster, especially when dealing with large archives. Details are provided [dotnet/runtime #103153](https://github.com/dotnet/runtime/pull/103153#issue-2339713028). |
| 129 | + |
| 130 | +## Additional `TryAdd` and `TryGetValue` overloads for `OrderedDictionary<TKey, TValue>` |
| 131 | + |
| 132 | +<xref:System.Collections.Generic.OrderedDictionary`2> provides `TryAdd` and `TryGetValue` for addition and retrieval like any other `IDictionary<TKey, TValue>` implementation. However, there are scenarios where you might want to perform additional operations, so new overloads have been added that return an index to the entry: |
| 133 | + |
| 134 | +```csharp |
| 135 | +public class OrderedDictionary<TKey, TValue> |
| 136 | +{ |
| 137 | + // New overloads |
| 138 | + public bool TryAdd(TKey key, TValue value, out int index); |
| 139 | + public bool TryGetValue(TKey key, out TValue value, out int index); |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +This index can then be used with <xref:System.Collections.Generic.OrderedDictionary`2.GetAt*>/<xref:System.Collections.Generic.OrderedDictionary`2.SetAt*> for fast access to the entry. An example usage of the new `TryAdd` overload is to add or update a key/value pair in the ordered dictionary: |
| 144 | + |
| 145 | +<!-- :::code language="csharp" source="../snippets/dotnet-10/csharp/snippets.cs" id="snippet2"::: --> |
| 146 | + |
| 147 | +```csharp |
| 148 | +// Try to add a new key with value 1. |
| 149 | +if (!orderedDictionary.TryAdd(key, 1, out int index)) |
| 150 | +{ |
| 151 | + // Key was present, so increment the existing value instead. |
| 152 | + int value = orderedDictionary.GetAt(index).Value; |
| 153 | + orderedDictionary.SetAt(index, value + 1); |
| 154 | +} |
| 155 | +``` |
| 156 | + |
| 157 | +This new API is already used in <xref:System.Json.JsonObject> and improves the performance of updating properties by 10-20%. |
| 158 | + |
| 159 | +## Allow specifying ReferenceHandler in `JsonSourceGenerationOptions` |
| 160 | + |
| 161 | +When using source generators for JSON serialization, the generated context will throw when cycles are serialized or deserialized. This behavior can now be customized by specifying the <xref:System.Text.Json.Serialization.ReferenceHandler> in the <xref:System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute>. Here is an example using `JsonKnownReferenceHandler.Preserve`: |
| 162 | + |
| 163 | +<!-- :::code language="csharp" source="../snippets/dotnet-10/csharp/snippets.cs" id="snippet3"::: --> |
| 164 | + |
| 165 | +```csharp |
| 166 | +public static void MakeSelfRef() |
| 167 | +{ |
| 168 | + SelfReference selfRef = new SelfReference(); |
| 169 | + selfRef.Me = selfRef; |
| 170 | + |
| 171 | + Console.WriteLine(JsonSerializer.Serialize(selfRef, ContextWithPreserveReference.Default.SelfReference)); |
| 172 | + // Output: {"$id":"1","Me":{"$ref":"1"}} |
| 173 | +} |
| 174 | + |
| 175 | +[JsonSourceGenerationOptions(ReferenceHandler = JsonKnownReferenceHandler.Preserve)] |
| 176 | +[JsonSerializable(typeof(SelfReference))] |
| 177 | +internal partial class ContextWithPreserveReference : JsonSerializerContext |
| 178 | +{ |
| 179 | +} |
| 180 | + |
| 181 | +internal class SelfReference |
| 182 | +{ |
| 183 | + public SelfReference Me { get; set; } = null!; |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +## More left-handed matrix transformation methods |
| 188 | + |
| 189 | +.NET 10 adds the remaining APIs for creating left-handed transformation matrices for billboard and constrained-billboard matrices. You can use these methods like their existing right-handed counterparts [add xrefs to the existing counterparts] when using a left-handed coordinate system instead. |
| 190 | + |
| 191 | +```csharp |
| 192 | +public partial struct Matrix4x4 |
| 193 | +{ |
| 194 | + public static Matrix4x4 CreateBillboardLeftHanded(Vector3 objectPosition, Vector3 cameraPosition, Vector3 cameraUpVector, Vector3 cameraForwardVector) |
| 195 | + public static Matrix4x4 CreateConstrainedBillboardLeftHanded(Vector3 objectPosition, Vector3 cameraPosition, Vector3 rotateAxis, Vector3 cameraForwardVector, Vector3 objectForwardVector) |
| 196 | +} |
| 197 | +``` |
0 commit comments