本文介绍适用于 .NET 10 的 .NET 库中的新功能。 它已针对预览版 4 进行更新。
密码学
按 SHA-1 以外的指纹查找证书
通过指纹唯一查找证书是一个相当常见的操作,但 X509Certificate2Collection.Find(X509FindType, Object, Boolean) 方法(用于 FindByThumbprint 模式)仅搜索 SHA-1 指纹值。
使用 Find
此方法查找 SHA-2-256(“SHA256”)和 SHA-3-256 指纹存在一些风险,因为这些哈希算法的长度相同。
相反,.NET 10 引入了一 个新方法 ,该方法接受用于匹配的哈希算法的名称。
X509Certificate2Collection coll = store.Certificates.FindByThumbprint(HashAlgorithmName.SHA256, thumbprint);
Debug.Assert(coll.Count < 2, "Collection has too many matches, has SHA-2 been broken?");
return coll.SingleOrDefault();
在 ASCII/UTF-8 中查找 PEM 编码的数据
PEM 编码(最初是 隐私增强邮件,但现在在电子邮件外部广泛使用)被定义为“文本”,这意味着该 PemEncoding 类旨在运行 String 和 ReadOnlySpan<char>
。 类似于证书的东西通常是写在使用 ASCII(字符串)编码的文件中,这在 Linux 上尤其常见。 从历史上看,这意味着你需要打开文件并将字节转换为字符(或字符串),然后才能使用 PemEncoding
。
新 PemEncoding.FindUtf8(ReadOnlySpan<Byte>) 方法利用了 PEM 仅为 7 位 ASCII 字符定义的事实,并且 7 位 ASCII 与单字节 UTF-8 值完全重叠。 通过调用此新方法,可以跳过 UTF-8/ASCII 到字符转换并直接读取文件。
byte[] fileContents = File.ReadAllBytes(path);
-char[] text = Encoding.ASCII.GetString(fileContents);
-PemFields pemFields = PemEncoding.Find(text);
+PemFields pemFields = PemEncoding.FindUtf8(fileContents);
-byte[] contents = Base64.DecodeFromChars(text.AsSpan()[pemFields.Base64Data]);
+byte[] contents = Base64.DecodeFromUtf8(fileContents.AsSpan()[pemFields.Base64Data]);
PKCS#12/PFX 导出的加密算法
新的 ExportPkcs12 方法使得 X509Certificate 允许调用方选择用于生成输出的加密和摘要算法:
- Pkcs12ExportPbeParameters.Pkcs12TripleDesSha1 指示 Windows XP 时代事实上的标准。 通过选择较旧的加密算法,它生成的输出几乎可以被所有支持读取 PKCS#12/PFX 的库和平台支持。
- Pkcs12ExportPbeParameters.Pbes2Aes256Sha256 指示应使用 AES 而不是 3DES(和 SHA-2-256 而不是 SHA-1),但所有读取器(如 Windows XP)可能无法理解输出。
如果想要更多控制,可以使用重载来接受PbeParameters。
全球化和日期/时间
ISOWeek for DateOnly 类型的新方法重载
最初设计的 ISOWeek 类主要是为了专门使用 DateTime,因为它是在 DateOnly 类型出现之前引入的。 既然DateOnly
现在可用了,那么ISOWeek
也有必要支持它。 以下重载是新引入的:
用于字符串比较的数字排序
数值字符串比较是一项高度请求的功能,用于在数字上而不是按词法比较字符串。 例如,2
小于10
,因此"2"
应按数字顺序在"10"
前显示。 同样,"2"
和 "02"
在数值上是相等的。 使用新 NumericOrdering 选项,现在可以执行以下类型的比较:
StringComparer numericStringComparer = StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.NumericOrdering);
Console.WriteLine(numericStringComparer.Equals("02", "2"));
// Output: True
foreach (string os in new[] { "Windows 8", "Windows 10", "Windows 11" }.Order(numericStringComparer))
{
Console.WriteLine(os);
}
// Output:
// Windows 8
// Windows 10
// Windows 11
HashSet<string> set = new HashSet<string>(numericStringComparer) { "007" };
Console.WriteLine(set.Contains("7"));
// Output: True
此选项不适用于以下基于索引的字符串操作:IndexOf
、LastIndexOf
、StartsWith
、EndsWith
、IsPrefix
和IsSuffix
。
具有单个参数的新 TimeSpan.FromMilliseconds
重载
以前引入了TimeSpan.FromMilliseconds(Int64, Int64)方法,但没有添加接受单个参数的重载。
尽管这是由于第二个参数是可选的,但在 LINQ 表达式中使用时会导致编译错误,例如:
Expression<Action> a = () => TimeSpan.FromMilliseconds(1000);
出现此问题的原因是 LINQ 表达式无法处理可选参数。 为了解决此问题,.NET 10 引入了 新的重载 采用单个参数。 它还修改 现有方法 ,使第二个参数是必需的。
字符串
用于处理字符范围的字符串规范化 API
Unicode 字符串规范化已长时间受支持,但现有 API 仅适用于字符串类型。 这意味着,具有以不同形式存储的数据(如字符数组或范围)的调用方必须分配一个新字符串才能使用这些 API。 此外,返回规范化字符串的 API 始终分配一个新字符串来表示规范化输出。
.NET 10 引入了处理字符范围的新 API,这些 API 将规范化扩展到字符串类型之外,并帮助避免不必要的分配:
- StringNormalizationExtensions.GetNormalizedLength(ReadOnlySpan<Char>, NormalizationForm)
- StringNormalizationExtensions.IsNormalized(ReadOnlySpan<Char>, NormalizationForm)
- StringNormalizationExtensions.TryNormalize(ReadOnlySpan<Char>, Span<Char>, Int32, NormalizationForm)
收集
为 OrderedDictionary<TKey, TValue>
添加额外的 TryAdd
和 TryGetValue
重载
OrderedDictionary<TKey,TValue> 提供 TryAdd
并 TryGetValue
像任何其他 IDictionary<TKey, TValue>
实现一样进行添加和检索。 但是,在某些情况下,你可能想要执行更多操作,因此会添加新的重载,以便返回一个条目的索引。
可以使用此索引与GetAt和SetAt一起快速访问条目。 新 TryAdd
重载的示例用法是在有序字典中添加或更新键值对:
// Try to add a new key with value 1.
if (!orderedDictionary.TryAdd(key, 1, out int index))
{
// Key was present, so increment the existing value instead.
int value = orderedDictionary.GetAt(index).Value;
orderedDictionary.SetAt(index, value + 1);
}
此新 API 已在使用中 JsonObject ,并通过 10-20%提高更新属性的性能。
序列化
允许在JsonSourceGenerationOptions
中指定 ReferenceHandler
使用 源生成器进行 JSON 序列化时,在序列化或反序列化周期时,生成的上下文将引发。 现在,您可以通过在 JsonSourceGenerationOptionsAttribute 中指定 ReferenceHandler 来自定义此行为。 以下是一个使用 JsonKnownReferenceHandler.Preserve
的例子:
public static void MakeSelfRef()
{
SelfReference selfRef = new SelfReference();
selfRef.Me = selfRef;
Console.WriteLine(JsonSerializer.Serialize(selfRef, ContextWithPreserveReference.Default.SelfReference));
// Output: {"$id":"1","Me":{"$ref":"1"}}
}
[JsonSourceGenerationOptions(ReferenceHandler = JsonKnownReferenceHandler.Preserve)]
[JsonSerializable(typeof(SelfReference))]
internal partial class ContextWithPreserveReference : JsonSerializerContext
{
}
internal class SelfReference
{
public SelfReference Me { get; set; } = null!;
}
System.Numerics
更多左手矩阵变换方法
.NET 10 添加了用于为广告牌和约束广告牌矩阵创建左手转换矩阵的剩余 API。 您可以像使用现有的右手坐标系那样,使用这些方法,例如,CreateBillboard(Vector3, Vector3, Vector3, Vector3),当使用左手坐标系时。
- Matrix4x4.CreateBillboardLeftHanded(Vector3, Vector3, Vector3, Vector3)
- Matrix4x4.CreateConstrainedBillboardLeftHanded(Vector3, Vector3, Vector3, Vector3, Vector3)
张量增强功能
该 System.Numerics.Tensors 接口现在包括一个非泛型接口IReadOnlyTensor,用于访问Lengths和Strides等操作。 切片操作现在不再复制数据,从而通过提高性能。 此外,当性能不是关键时,您可以通过将其装箱到 object
来访问非泛型数据。
选项验证
新的 AOT 安全构造函数 ValidationContext
在选项验证期间使用的 ValidationContext 类包含一个新构造函数重载,该重载显式地接受 displayName
参数:
ValidationContext(Object, String, IServiceProvider, IDictionary<Object,Object>)
显示名称可确保 AOT 安全,并在本机版本中启用其使用,而无需警告。
诊断
对ActivitySource
和Meter
中的遥测架构URL的支持
ActivitySource 和 Meter 现在在构造过程中支持指定遥测架构 URL,这与 OpenTelemetry 规范保持一致。 遥测架构可确保跟踪和指标数据的一致性和兼容性。 此外,.NET 10 引入了ActivitySourceOptions,它简化了使用多个配置选项(包括遥测架构 URL)创建ActivitySource实例。
新 API 包括:
- ActivitySource(ActivitySourceOptions)
- ActivitySource.TelemetrySchemaUrl
- Meter.TelemetrySchemaUrl
- ActivitySourceOptions
用于活动事件和链接的进程外跟踪支持
该 Activity 类通过跟踪跨服务或组件的作流来启用分布式跟踪。 .NET 支持通过 Microsoft-Diagnostics-DiagnosticSource
事件源提供程序将此跟踪数据进行进程外序列化。
Activity
可以包含其他元数据,例如 ActivityLink 和 ActivityEvent。 .NET 10 增加了对这些链接和事件的序列化支持,因此,外部进程跟踪数据现在包含该信息。 例如:
Events->"[(TestEvent1,2025-03-27T23:34:10.6225721+00:00,[E11:EV1,E12:EV2]),(TestEvent2,2025-03-27T23:34:11.6276895+00:00,[E21:EV21,E22:EV22])]"
Links->"[(19b6e8ea216cb2ba36dd5d957e126d9f,98f7abcb3418f217,Recorded,null,false,[alk1:alv1,alk2:alv2]),(2d409549aadfdbdf5d1892584a5f2ab2,4f3526086a350f50,None,null,false)]"
速率限制追踪采样支持
当分布式跟踪数据通过 Microsoft-Diagnostics-DiagnosticSource
事件源提供程序在进程外序列化时,可以发出所有记录的活动,或者可以根据跟踪比率应用采样。
名为 “速率限制采样 ”的新采样选项限制每秒序列化的 根活动 数。 这有助于更准确地控制数据量。
进程外跟踪数据聚合器可以通过在FilterAndPayloadSpecs
中指定选项来启用和配置此采样。 例如,以下设置将所有实例的序列化限制为每秒 ActivitySource
100 个根活动:
[AS]*/-ParentRateLimitingSampler(100)
ZIP 文件
ZipArchive 性能和内存改进
.NET 10 提高了 ZipArchive 的性能和内存使用率。
首先,已经优化了条目在Update
模式下写入ZipArchive
的方式。 以前,所有 ZipArchiveEntry 实例都加载到内存中并重写,这可能会导致内存使用率和性能瓶颈较高。 优化可减少内存使用率,并避免需要将所有条目加载到内存中,从而提高性能。
其次,条目的 ZipArchive 提取现已并行化,并且内部数据结构经过优化,以便更好地使用内存。 这些改进解决了与性能瓶颈和高内存使用率相关的问题,提高了 ZipArchive
效率和速度更快,尤其是在处理大型存档时。
新的异步 ZIP API
.NET 10 引入了新的异步 API,使读取或写入 ZIP 文件时执行非阻塞作更容易。 社区高度要求此功能。
新 async
方法可用于提取、创建和更新 ZIP 存档。 这些方法使开发人员能够有效地处理大型文件并提高应用程序响应能力,尤其是在涉及 I/O 绑定作的方案中。 这些方法包括:
- ZipArchive.CreateAsync(Stream, ZipArchiveMode, Boolean, Encoding, CancellationToken)
- ZipArchiveEntry.OpenAsync(CancellationToken)
- ZipFile.CreateFromDirectoryAsync
- ZipFile.ExtractToDirectoryAsync
- ZipFile.OpenAsync
- ZipFile.OpenReadAsync(String, CancellationToken)
- ZipFileExtensions.CreateEntryFromFileAsync
- ZipFileExtensions.ExtractToDirectoryAsync
- ZipFileExtensions.ExtractToFileAsync
有关使用这些 API 的示例,请参阅 预览版 4 博客文章。
GZipStream 中连接流的性能改进
社区的一项贡献改进了 GZipStream 在处理串联 GZip 数据流时的性能。 以前,每个新流段都会处理并重新分配内部 ZLibStreamHandle
,这导致额外的内存分配和初始化开销。 通过此更改,该句柄现已重置并重复使用,以减少托管和非托管内存分配并提高执行时间。 处理大量小型数据流时,可以看到影响最大(约 35% 更快)。 这项更改:
- 消除每个串联流大约 64-80 字节内存的重复分配,同时节省额外的非托管内存。
- 减少每个串联流的执行时间大约为 400 纳秒。