Skip to content

.NET 8 新特性

2023 年 11 月 14 日,.NET 8 正式发布

下载 .NET 8.0

.NET 8 是 .NET 7 的后继版本。 它将作为长期支持 (LTS) 版本得到三年的支持

使用主构造函数

可以将参数添加到 struct 或 class 声明中,用于创建主构造函数。 主构造函数参数在整个类定义范围内。 请务必将主构造函数参数视为参数,即使它们在整个类定义范围内也是如此。 有几个规则阐明了它们是参数:

  • 如果不需要主构造函数参数,可能不会存储它们。
  • 主构造函数参数不是类的成员。 例如,名为 param 的主构造函数参数不能作为 this.param 访问。
  • 可以对主构造函数参数进行分配。
  • 主构造函数参数不会成为属性,record 类型除外。

依赖关系注入

c#
public interface IService
{
    Distance GetDistance();
}

public class ExampleController(IService service) : ControllerBase
{
    [HttpGet]
    public ActionResult<Distance> Get()
    {
        return service.GetDistance();
    }
}

初始化基类

c#
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

之前的写法:

c#
services.AddAuthorization(options =>
{
    options.AddPolicy(AuthorizePolicy.Default, policy =>
    {
        policy.Requirements.Add(new PermissionRequirement());
    });
});

.NET 8.0 写法

c#
services.AddAuthorizationBuilder().AddPolicy(AuthorizePolicy.Default, policy =>
{
    policy.Requirements.Add(new PermissionRequirement());
});

使用最小 API 进行防伪造

此版本添加了用于验证反伪令牌的中间件,用于缓解跨站点请求伪造攻击。 调用 AddAntiforgery 以在 DI 中注册防伪服务。 在 DI 容器中注册防伪服务时,WebApplicationBuilder 会自动添加防伪中间件。 防伪令牌用于减少跨站点请求伪造攻击。

c#
var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery(); 

var app = builder.Build();

app.UseAntiforgery(); 

app.MapGet("/", () => "Hello World!");

app.Run();

使用集合表达式

c#
// 初始化集合
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=true3.76 MB1.84 MB
Windows x642.85 MB1.77 MB
  • 允许指定优化首选项:大小或速度。 默认情况下,编译器选择以生成快速代码,同时请注意应用程序的大小。 但是,可以使用 <OptimizationPreference> MSBuild 属性专门针对一个或另一个进行优化。

.NET 容器镜像

  • 容器映像现在使用 Debian 12 (Bookworm)。 Debian 是 .NET 容器映像中的默认 Linux 发行版。

  • 映像包括一个 non-root 用户。 该用户使映像具有 non-root 功能。 若要以 non-root 运行,请在 Dockerfile 的末尾添加以下行(或在 Kubernetes 清单中添加类似指令):

    Dockerfile
    USER app

    默认端口也从端口 80 更改为 8080。 为支持此更改,可以使用新的环境变量 ASPNETCORE_HTTP_PORTS 来更轻松地更改端口。 该变量接受端口列表,这比 ASPNETCORE_URLS 所需的格式更简单。 如果使用这些变量之一将端口更改回端口 80,则将无法以 non-root 运行。

  • 预览版容器映像标记现在有一个 -preview 后缀,而不仅仅是使用版本号。 例如,若要拉取 .NET 8 预览版 SDK,请使用以下标记:

    sh
    docker 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.RandomSystem.Security.Cryptography.RandomNumberGenerator 类型引入了两种处理随机性的新方法。

C#
GetItems<T>()

新的 System.Random.GetItemsSystem.Security.Cryptography.RandomNumberGenerator.GetItems 方法可用于从输入集中随机选择指定数量的项。 以下示例显示如何使用 System.Random.GetItems<T>()(在 Random.Shared 属性提供的实例上)将 31 项随机插入数组。 此示例可用于“Simon”游戏,在此游戏中,玩家必须记住一系列彩色按钮。

C#
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 ...
C#
Shuffle<T>()

新的 Random.ShuffleRandomNumberGenerator.Shuffle<T>(Span<T>) 方法可用于随机化范围的顺序。 这些方法对于减少机器学习中的训练偏差很有用(因此,第一件事并不总是训练,但最后一件事总是测试)。

C#
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.IndexOfAnyMemoryExtensions.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.XxHash3System.IO.Hashing.XxHash128 类型可实现快速 XXH3 和 XXH128 哈希算法。

JSON 序列化改进

  • 添加对 JsonUnmappedMemberHandling 的支持
  • 源生成器支持 requiredinit 属性
  • 接口层次结构支持
  • Snake CaseKebab 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。

C#
GC.RefreshMemoryLimit();

还可以刷新与内存限制相关的一些 GC 配置设置。 以下代码片段将堆硬限制设置为 100 兆字节 (MiB):

C#
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 来选择退出验证。

你觉得这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度