.NET 9就要来了,将带来许多变化和改进。这篇文章将带你了解.NET 9 和 C# 13 中的最具影响力和最常用的功能。
1. 锁实例C# 13 引入了一种新的 System.Threading.Lock
类型,用于实现互斥。过去,通常使用 object
类型进行加锁,而现在有了专门的 Lock
类型,预计将成为大多数互斥场景中的标准选择。
- 更干净、更安全的代码。 它使代码更易读和更可预测。此外,编译器会在
Lock
实例被错误地用作常规object
时发出警告。 - 性能。 根据微软的说法,它比在任意
object
实例上加锁更高效。 - 新的加锁机制。
EnterScope
在底层替代了Monitor
类。你可以在这里找到上面示例的 IL 代码。它返回一个遵循Dispose
模式的ref struct
,可以无缝地与using
语句一起使用。 - 异步限制。 由于锁和异步代码交互的固有局限性,在锁块中仍然不允许进行异步调用。传统上,
SemaphoreSlim
是首选的替代方案。
想象你有一份任务清单,每个任务完成的时间各不相同。你希望每个任务一完成后就立即处理。WaitAll()
不适用于这种情况,因为它会等待所有任务全部完成。我们可以使用 Task.WaitAny()
作为替代手段来处理下一个完成的任务。C# 10 引入了一种更优雅和高效的方式来处理这种情况:Task.WhenEach
。下面是一个示例。
Task.WhenEach
返回 IAsyncEnumerable<Task<TResult>>
,并且可以利用 await foreach
轻松地遍历随着它们的完成的每个任务👌
从 C# 13 版本开始,params
参数可以是任何可以在集合表达式中使用的类型。
- 更清晰的代码。 显著减少了对
.ToArray()
和.ToList()
的调用次数。 - 性能。 调用
.ToArray()
和.ToList()
本身就会增加额外的资源开销。此外,现在支持传递Span<>
和IEnumerable<>
,利用更高效的内存使用和惰性执行。总体而言,在需要更好性能的场景下,这提供了更大的灵活性和更好的性能表现 🚀。
当你在 C# 中声明一个自动实现的属性,比如 public int Number { get; set; }
时,编译器会生成一个字段(例如 _number
)和内部的 getter 和 setter 方法,比如 void set_Number(int number)
和 int get_Number()
。但是,如果你需要自定义逻辑,比如验证、默认值、计算或延迟加载等,通常需要手动在类中定义字段。C# 13 通过引入 field
关键字简化了这个过程,允许你直接访问字段而无需手动定义它。
- 减少样板代码。 消除了手动定义私有字段的需要,从而产生更干净、更简洁的代码效果。
- 增强可读性。 无需管理自定义的后备字段名称,使使用字段关键字成为标准做法,从而提高代码的清晰度。
- 属性范围内的字段。 私有后备字段仅限于属性本身,防止在类的其他部分意外使用。这种封装提高了安全性,并解决了常见的错误来源。
- 🚨潜在的破坏性变更。 如果您的类已经有一个名为
field
且类型相同的属性,它将优先于新的field
关键字,可能导致意外行为或错误。您可以在此处查看C# 团队的提案关于如何处理此问题的建议。这可能是自 2016 年首次提出该功能以来一直被推迟的原因之一,也是该功能迟迟未能发布的因素之一。
新的 HybridCache
API 解决了现有 IDistributedCache
和 IMemoryCache
API 中的一些不足,如竞态条件,并引入了新的功能和能力,使 .NET 中的缓存更加灵活且性能更高。HybridCache
被设计为大多数 IDistributedCache
和 IMemoryCache
场景的即插即用替代品。
- 两全其美。
HybridCache
允许您使用统一、简洁的 API 将数据存储在内存(L1)或分布式(L2)缓存中。这种配置允许您快速访问常用的本地数据(L1),同时提供一个可扩展的外部缓存(L2)来处理较大的、不常访问的数据。[HybridCacheEntryFlags](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.hybrid.hybridcacheentryflags?view=netframework-4.8.1)
用于控制这种行为。 - 防止踩踏效应。
IMemoryCache
和IDistributedCache
都存在踩踏问题。HybridCache
通过确保只有一个请求者重新生成给定键的值,而其他请求者等待结果,来解决这个问题。这有助于避免缓存过度刷新。 - 额外功能。
HybridCache
提供了诸如标记、可配置的序列化 通过使用.WithSerializer(...)
和.WithSerializerFactory(...)
方法,以及通过使用[ImmutableObject(true)]
注解的 缓存实例复用 等额外功能。
💡乍一听可能有点复杂,但要让 HybridCache
将数据存储在进程外部(L2),你仍然需要配置一个 IDistributedCache
(例如 Redis),HybridCache
会依赖于它。即使没有 IDistributedCache
,HybridCache
仍然会提供进程内的缓存。在我上面提到的情况下,我有如下配置:
从 .NET 5 版本开始,Web API 模板自带了对 OpenApi
规范的内置支持,通过 [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)
包。在 .NET 9 中,微软继续通过其内部开发的 [Microsoft.AspNetCore.OpenApi](https://github.com/dotnet/aspnetcore/tree/main/src/OpenApi)
包支持 OpenApi
规范,并且该包将取代 [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)
包。
运行你的应用之后,前往 /openapi/v1.json
页面查看生成的 OpenApi
文档内容。
- Swagger UI. 语法变得更简洁,乍一看更像‘原生’。但是,默认情况下,你只能得到一个
OpenAPI
文档,并没有包含交互式的 API 文档😢。如果你需要像Swagger UI
这样的交互式 API 文档,你可能需要集成第三方工具,例如[Scalar](https://github.com/scalar/scalar)
来实现。这里有一份详细的指南:Scalar .NET API Reference Integration。 - 构建时间生成。 你可以使用
[Microsoft.Extensions.ApiDescription.Server](https://github.com/dotnet/aspnetcore/tree/main/src/Tools/Extensions.ApiDescription.Server)
包在构建时生成 OpenAPI 文档。
SearchValues
在 .NET 8 中引入,提供了一个不可变的、只读的值类,与传统的 ICollection.Contains
方法相比,它在搜索效率上有显著提升。最初,它仅限于字符或字节类型。在 .NET 9 中,SearchValues<T>
也支持字符串类型。
你现在也可以通过 StringComparison
参数来指定比较类型。
将来,这将成为需要进行大量文本处理的应用程序的首选,例如文档解析、输入过滤、垃圾邮件检测、数据脱敏和搜索等。
8. 新的 LINQ 方法(或功能),确保“LINQ”未被翻译,保持英文原样。此处已按专家建议调整为更口语化且更具清晰度的表达方式,同时添加了逗号以匹配源文本的风格。
新加入了三种方法:CountBy
、AggregateBy
和 Index
,它们旨在提高处理常见数据操作任务的效率和简洁性。下面的示例将展示这些方法的具体用法。
计数
按聚合目录
我最喜欢的 Index()
💛 方法是因为 foreach
没有索引,这一直以来都让人很烦恼,并且常常导致使用更复杂的替代方案。
自从.NET早期以来,我们就使用Guid.NewGuid()
来生成UUID。此方法生成的是版本4的UUID。自那以后,UUID规范有了显著的进展,目前的稳定版本是7。版本7的一个关键特性是包含在UUID中的时间戳。它的结构如下:
+------------------+---------------+----------------------+
| 48位时间戳字段 | 12位随机数 | 62位另一组随机数 |
+------------------+---------------+----------------------+
说明:上表展示了字段的位数分配,其中48位用于时间戳字段,12位用于随机数,62位用于另一组随机数。
时间戳部分带来了重要的好处:可以根据其创建时间对UUID进行排序。这使得它们更适合作为数据库中的标识符,并在分布式环境中提供了更好的唯一性保证。
现在 .NET 已经内置了 UUID v7 的生成,因此你不再需要使用外部库如 [UUIDNext](https://github.com/mareek/UUIDNext)
来实现这一功能。引入了一个新的方法 Guid.CreateVersion7()
,此方法还可以接受一个时间戳参数,允许你创建与特定时间相匹配的 UUID。这在测试或将项目插入已排序序列的特定位置时非常有用。
⏳实际上,Guid.CreateVersion7()
会在内部使用NewGuid()
。它包含了一个48位的时间戳,并设置了版本和变体位,使其符合UUIDv7标准。这会使它稍微比NewGuid()
慢一些。这点需要注意,但是这样的性能影响通常可以忽略不计。
以下是一些其他有趣的更改,绝对不容错过,但可能不会被广泛采用,而是解决更为具体的问题场景。
- 隐式索引访问
- 部分属性
- 支持 ref 结构体
- Base64Url (Base64URL 编码)
- 使用跨度的集合查找功能
- 支持剪枝的特性开关
- Regex.EnumerateSplits 方法
- System.Text.Json 库的新功能
- 有序的键值对字典<TKey, TValue>
- ReadOnlySet<T>
- Debug.Assert 现在报告断言失败
- 新的 Tensor<T> 类型支持
觉得有帮助就点赞👏和反馈哦!非常宝贵!