.NET 8
新特性
2023 年 11 月 14 日,.NET 8 正式发布
.NET 8 是 .NET 7 的后继版本。 它将作为长期支持 (LTS) 版本得到三年的支持
使用主构造函数
可以将参数添加到 struct 或 class 声明中,用于创建主构造函数。 主构造函数参数在整个类定义范围内。 请务必将主构造函数参数视为参数,即使它们在整个类定义范围内也是如此。 有几个规则阐明了它们是参数:
- 如果不需要主构造函数参数,可能不会存储它们。
- 主构造函数参数不是类的成员。 例如,名为 param 的主构造函数参数不能作为 this.param 访问。
- 可以对主构造函数参数进行分配。
- 主构造函数参数不会成为属性,
record
类型除外。
依赖关系注入
public interface IService
{
Distance GetDistance();
}
public class ExampleController(IService service) : ControllerBase
{
[HttpGet]
public ActionResult<Distance> Get()
{
return service.GetDistance();
}
}
初始化基类
public class BankAccount(string accountID, string owner)
{
public string AccountID { get; } = accountID;
public string Owner { get; } = owner;
public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}";
}
使用 AddAuthorizationBuilder 注册授权服务并构造策略。
可不再使用 AddAuthorization
,而是转换为使用新的 AddAuthorizationBuilder
。
之前的写法:
services.AddAuthorization(options =>
{
options.AddPolicy(AuthorizePolicy.Default, policy =>
{
policy.Requirements.Add(new PermissionRequirement());
});
});
.NET 8.0 写法
services.AddAuthorizationBuilder().AddPolicy(AuthorizePolicy.Default, policy =>
{
policy.Requirements.Add(new PermissionRequirement());
});
使用最小 API 进行防伪造
此版本添加了用于验证反伪令牌的中间件,用于缓解跨站点请求伪造攻击。 调用 AddAntiforgery
以在 DI 中注册防伪服务。 在 DI 容器中注册防伪服务时,WebApplicationBuilder
会自动添加防伪中间件。 防伪令牌用于减少跨站点请求伪造攻击。
var builder = WebApplication.CreateBuilder();
builder.Services.AddAntiforgery();
var app = builder.Build();
app.UseAntiforgery();
app.MapGet("/", () => "Hello World!");
app.Run();
使用集合表达式
// 初始化集合
List<string> list1 = [];
List<string> list2 = [];
List<string> list3 = [];
// 拼接多个集合
List<string> list = [.. list1, .. list2, .. list3];
本机 AOT 支持
发布为本机 AOT 的选项最初是在 .NET 7 中引入的。 使用本机 AOT 发布应用会创建一个完全独立的应用版本,该版本不需要运行时,所有内容都包含在一个文件中。 .NET 8 为本机 AOT 发布带来了以下改进:
添加了对 macOS 上的 x64 和 Arm64 体系结构的支持。
Linux 上本机 AOT 应用的大小最多可缩小 50%。 下表显示了在 .NET 7 和 .NET 8 上使用包含整个 .NET 运行时的本机 AOT 发布的“Hello World”应用的大小:
操作系统 | .NET 7 | .NET 8 预览版 1 |
---|---|---|
Linux x64(具有 -p:StripSymbols=true ) | 3.76 MB | 1.84 MB |
Windows x64 | 2.85 MB | 1.77 MB |
- 允许指定优化首选项:大小或速度。 默认情况下,编译器选择以生成快速代码,同时请注意应用程序的大小。 但是,可以使用
<OptimizationPreference>
MSBuild 属性专门针对一个或另一个进行优化。
.NET 容器镜像
容器映像现在使用 Debian 12 (Bookworm)。 Debian 是 .NET 容器映像中的默认 Linux 发行版。
映像包括一个 non-root 用户。 该用户使映像具有 non-root 功能。 若要以 non-root 运行,请在 Dockerfile 的末尾添加以下行(或在 Kubernetes 清单中添加类似指令):
DockerfileUSER app
默认端口也从端口 80 更改为 8080。 为支持此更改,可以使用新的环境变量 ASPNETCORE_HTTP_PORTS 来更轻松地更改端口。 该变量接受端口列表,这比 ASPNETCORE_URLS 所需的格式更简单。 如果使用这些变量之一将端口更改回端口 80,则将无法以 non-root 运行。
预览版容器映像标记现在有一个 -preview 后缀,而不仅仅是使用版本号。 例如,若要拉取 .NET 8 预览版 SDK,请使用以下标记:
shdocker run --rm -it mcr.microsoft.com/dotnet/sdk:8.0-preview
对于候选发布 (RC) 版本,将删除 -preview 后缀。
Chiseled Ubuntu
映像可用于 .NET 8。 Chiseled 映像的受攻击面较小,因为它们超级小,没有包管理器或 shell,并且是 non-root。 此类映像适用于希望获得设备式计算优势的开发人员。 Chiseled 映像发布到 .NET 夜间项目注册表。
.NET 在 Linux 上生成
在以前的 .NET 版本中,可以从源生成 .NET,但需要从与发布相对应的 dotnet/installer 存储库提交创建“源 tarball”。 在 .NET 8 中,不再需要这样操作,你可以直接从 dotnet/dotnet 存储库在 Linux 上生成 .NET。 该存储库使用 dotnet/source-build 生成 .NET 运行时、工具和 SDK。 这是 Red Hat 和 Canonical 用于生成 .NET 的同一内部版本。
对于大多数人来说,在容器中生成是最简单的方法,因为 dotnet-buildtools/prereqs 容器映像包含所有必需的依赖项。
Linux 的最低支持基线
适用于 .NET 8 的 Linux 最低支持基线已更新:
- .NET 将面向 Ubuntu 16.04 生成,适用于所有体系结构。 这对于定义 .NET 8 的最低 glibc 版本非常重要。 例如,.NET 8 甚至无法在 Ubuntu 14.04 上启动。
- 对于 Red Hat Enterprise Linux (RHEL),.NET 支持 RHEL 8+,删除 RHEL 7。
处理随机性的方法
System.Random
和 System.Security.Cryptography.RandomNumberGenerator
类型引入了两种处理随机性的新方法。
GetItems<T>()
新的 System.Random.GetItems
和 System.Security.Cryptography.RandomNumberGenerator.GetItems
方法可用于从输入集中随机选择指定数量的项。 以下示例显示如何使用 System.Random.GetItems<T>()
(在 Random.Shared
属性提供的实例上)将 31 项随机插入数组。 此示例可用于“Simon”游戏,在此游戏中,玩家必须记住一系列彩色按钮。
private static ReadOnlySpan<Button> s_allButtons = new[]
{
Button.Red,
Button.Green,
Button.Blue,
Button.Yellow,
};
...
Button[] thisRound = Random.Shared.GetItems(s_allButtons, 31);
// Rest of game goes here ...
Shuffle<T>()
新的 Random.Shuffle
和 RandomNumberGenerator.Shuffle<T>(Span<T>)
方法可用于随机化范围的顺序。 这些方法对于减少机器学习中的训练偏差很有用(因此,第一件事并不总是训练,但最后一件事总是测试)。
YourType[] trainingData = LoadTrainingData();
Random.Shared.Shuffle(trainingData);
IDataView sourceData = mlContext.Data.LoadFromEnumerable(trainingData);
DataOperationsCatalog.TrainTestData split = mlContext.Data.TrainTestSplit(sourceData);
model = chain.Fit(split.TrainSet);
IDataView predictions = model.Transform(split.TestSet);
// ...
以性能为中心的类型
.NET 8 引入了几种旨在提高应用性能的新类型。
新的
System.Collections.Frozen
命名空间包括集合类型FrozenDictionary<TKey,TValue>
和FrozenSet<T>
。 创建集合后,这些类型就不允许对键和值进行任何更改。 此要求可实现更快的读取操作(例如,TryGetValue())
。 对于在首次使用时填充,然后在长期服务期间保留的集合,这些类型特别有用,例如:C#private static readonly FrozenDictionary<string, bool> s_configurationData = LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true); // ... if (s_configurationData.TryGetValue(key, out bool setting) && setting) { Process(); }
新
System.Buffers.IndexOfAnyValues<T>
类型旨在传递给在传递的集合中查找任何值的第一个匹配项的方法。 例如,String.IndexOfAny(Char[])
在调用它的string
中查找指定数组中任何字符的第一个匹配项。 NET 8 添加了新的方法重载,例如接受新类型实例的String.IndexOfAny
和MemoryExtensions.IndexOfAny
。 创建System.Buffers.IndexOfAnyValues<T>
的实例时,将在那时派生优化后续搜索所需的所有数据,这意味着工作是预先完成的。新的 System.Text.CompositeFormat 类型可用于优化编译时未知的格式字符串(例如,格式字符串是从资源文件加载的)。 前面会花费一些额外的时间来完成诸如分析字符串之类的工作,但这可以节省每次使用时完成的工作。
C#private static readonly CompositeFormat s_rangeMessage = CompositeFormat.Parse(LoadRangeMessageResource()); // ... static string GetMessage(int min, int max) => string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
新的
System.IO.Hashing.XxHash3
和System.IO.Hashing.XxHash128
类型可实现快速 XXH3 和 XXH128 哈希算法。
JSON 序列化改进
- 添加对
JsonUnmappedMemberHandling
的支持 - 源生成器支持
required
和init
属性 - 接口层次结构支持
Snake Case
和Kebab Case
- 添加
JsonSerializer.MakeReadOnly()
和IsReadOnly
API
性能改进
.NET 8 包括对代码生成和实时 (JIT) 编译的改进:
- Arm64 性能改进
- SIMD 改进
- 云原生改进
- 按配置优化 (PGO) 改进
- 支持 AVX-512 ISA 扩展
- JIT 吞吐量改进
- JIT 吞吐量改进
垃圾回收
.NET 8 添加了动态调整内存限制的功能。 这在需求时有时无的云服务方案中非常有用。 为了提高成本效益,服务应随着需求波动而对资源消耗进行纵向扩展和缩减。 当服务检测到需求下降时,它可以通过降低内存限制来纵向缩减资源消耗。 以前,此操作可能会失败,因为垃圾回收器 (GC) 不了解这种变更,可能会分配比新限制更多的内存。 通过此更改,可以调用 RefreshMemoryLimit()
API 来使用新的内存限制更新 GC。
有一些限制需要注意:
在 32 位平台上(例如 Windows x86 和 Linux ARM),.NET 无法建立新的堆硬限制(如果还没有)。
API 可能会返回指示刷新失败的非零状态代码。 如果纵向缩减过于激进,并且 GC 没有回旋余地,则可能会发生这种情况。 在这种情况下,请考虑调用
GC.Collect(2, GCCollectionMode.Aggressive)
以收缩当前内存使用量,然后重试。如果纵向扩展内存限制超出 GC 认为进程在启动期间可以处理的大小,则
RefreshMemoryLimit
调用将成功,但它使用的内存不能超过它所认为的限制。
下面的代码片段演示如何调用 API。
GC.RefreshMemoryLimit();
还可以刷新与内存限制相关的一些 GC 配置设置。 以下代码片段将堆硬限制设置为 100 兆字节 (MiB):
AppContext.SetData("GCHeapHardLimit", (ulong)100 * 1_024 * 1_024);
GC.RefreshMemoryLimit();
如果硬性限制无效,例如,在堆硬性限制百分比为负值以及硬性限制太低的情况下,API 可能会引发 InvalidOperationException
异常。 如果刷新将设置的堆硬性限制(由于新的 AppData 设置或容器内存限制更改所暗示)低于已提交的值,则可能会发生这种情况。
热重载支持修改泛型
从 .NET 8 开始,C# 热重载支持修改泛型类型和泛型方法。 使用 Visual Studio 调试控制台、桌面、移动或 WebAssembly 应用程序时,可以在 C# 代码或 Razor 页面中对泛型类和泛型方法应用更改。
NuGet
从 .NET 8 开始,NuGet 默认在 Linux 上验证已签名的包。 NuGet 还会继续在 Windows 上验证已签名的包。
大多数用户不会注意到此验证。 但是,如果现有根证书捆绑包位于 /etc/pki/ca-trust/extracted/pem/objsign-ca-bundle.pem
,则可能会看到信任失败并伴有警告 NU3042。
可以通过将环境变量 DOTNET_NUGET_SIGNATURE_VERIFICATION
设置为 false 来选择退出验证。