.NET 6 预览版 7 发布——最后一个预览版

原文:bit.ly/2VJxjxQ
做者:Richard
翻译:精致码农-王亮
说明:文中有大量的超连接,这些连接在公众号文章中被自动剔除,一部分包含超连接列表的小段落被我删减了,若是你对此感兴趣,请参考阅读原文。linux

咱们很高兴地发布了 .NET 6 预览版 7。这是咱们进入(两个)候选发布版(RC)以前的最后一个预览版。在咱们放慢发布速度以前,团队一直在萤窗雪案,以完成最后一组功能。在这个版本中,你将看到各功能的最后一次抛光,一次到位地整合整个版本的大型功能。今后时起,团队将专一于使全部的功能达到统一的(高)质量,以便 .NET 6 为你的生产工做作好准备。ios

关于生产工做的话题,值得提醒你们的是,.NET 官网[1]和 Bing.com 从预览版 1 开始就一直运行在 .NET 6 上。咱们正在与不一样的团队(微软和其余公司)商谈有关进入生产的 .NET 6 RC 版本。若是你对此感兴趣,并但愿获得相关的指导,请联系 dotnet@microsoft.com。咱们始终很乐意与早期采用者交流。git

你能够在这下载[2] Linux、macOS 和 Windows 的 .NET 6 预览版 7。github

- 安装程序和二进制文件
  https://dotnet.microsoft.com/download/dotnet/6.0
- 容器镜像
  https://hub.docker.com/_/microsoft-dotnet
- Linux 包
  https://github.com/dotnet/core/blob/main/release-notes/6.0/install-linux.md
- 发布说明
  https://github.com/dotnet/core/blob/main/release-notes/6.0/README.md
- API 差别
  https://github.com/dotnet/core/tree/main/release-notes/6.0/preview/api-diff/preview7
- 已知问题
  https://github.com/dotnet/core/blob/main/release-notes/6.0/known-issues.md
- GitHub issue 跟踪
  https://github.com/dotnet/core/issues/6554

请参阅 .NET MAUI[3] 和 ASP.NET Core[4],了解更多关于客户端和 Web 应用场景的新内容。web

.NET 6 预览版 7 已经在 Visual Studio 2022 预览版 3 中测试经过并获得支持。Visual Studio 2022 使你可以利用为 .NET 6 开发的 Visual Studio 工具,如 .NET MAUI 的开发、C# 应用程序的 Hot Reload、WebForms 的 Web Live,以及 IDE 体验中的其余性能改进。Visual Studio Code 也支持 .NET 6。算法

请查看咱们新的对话帖[5],了解工程师之间关于最新的 .NET 功能的深刻讨论。咱们还发表了关于 C# 10 中的字符串插值和 .NET 6 中的预览功能 - 泛型 Math[6]docker

.NET SDK:现代化的 C# 项目模板

咱们更新了 .NET SDK 的模板,以使用最新的 C# 语言特性和模式。咱们已经有一段时间没有在新的语言特性方面从新审视这些模板了。如今是时候了,咱们将确保模板在将来使用新的功能。json

如下是新模板中使用的语言特性:api

  • 顶层语句
  • async Main
  • 全局 using 指令(经过 SDK 驱动的默认值)
  • File-scoped 命名空间
  • 目标类型 new 表达式
  • 可空(Nullable)引用类型

你可能会问,为何咱们要经过模板启用某些功能,而不是在项目以 .NET 6 为 Target 时默认启用这些功能。尽管咱们能够要求你在升级应用程序到新版本的 .NET 时作一些工做,做为改善平台默认行为的交换条件,这使咱们可以改进产品,而不会使项目文件随着时间的推移而变得复杂。然而,有些功能对于这种模式来讲多是至关具备破坏性的,好比可空(Nullable)的引用类型。不管是在何时,咱们都不想把这些功能与升级体验联系在一块儿,而是想把这个选择权留给你。模板是一个风险更低的支点,在那里咱们可以为新的代码设置新的“好的默认模型”,而不会产生那么多下游的后果。经过项目模板启用这些功能,咱们获得了一箭双鵰的结果:新代码开始时启用了这些功能,但现有的代码在你升级时不会受到影响。数组

控制台模板

控制台模板变化最大,经过顶层语句和全局引用指令,它如今是一个单行代码:

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

而之前的同一模板的 .NET 5 版本是这样的:

using System;

namespace Company.ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

控制台模板的项目文件也发生了变化,启用了可空(Nullable)引用类型的功能,例如:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

其余模板也能够实现可空(Nullable)引用类型、隐式全局引用和 File-scoped 命名空间,包括 ASP.NET Core 和 类库。

ASP.NET Web 模板

Web 模板也一样减小了代码行数,使用一样的功能:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

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

app.Run();

ASP.NET MVC 模板

MVC 模板的结构也相似。在这种状况下,咱们将 Program.csStartup.cs 合并为一个文件(Program.cs),造成了进一步的简化:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

模板兼容性

关于使用新模板的兼容性问题,请参见如下内容:

  • 模板中的 C# 代码不被早期的 .NET 版本所支持[7]
  • 隐式命名空间引入[8]

类库:反射 API 的可空性信息

可空引用类型[9]是编写可靠代码的一个重要特征。它在编写代码时很是有用,但在检查代码时却没有(直到如今)。新的反射 API[10] 使你可以肯定一个给定方法的参数和返回值的可空性。这些新的 API 对于基于反射的工具和序列化来讲是相当重要的,好比说:

就上下文而言,咱们在 .NET 5 中为 .NET 库添加了可空标注[11](在.NET 6 中完成),而且正在为 ASP.NET Core 的这个版本作一样的工做。咱们也看到开发者在他们的项目中采用了可空性(Nullability)[12]

可空性(Nullability)信息存在于使用自定义属性的元数据中[13]。原则上,任何人都已经能够读取自定义属性,然而,这并不理想,由于编码的消耗非同小可。

下面的例子演示了在几个不一样的场景中使用新的 API。

获取顶层的可空性信息

想象一下,你正在实现一个序列化器。使用这些新的 API,序列化器能够检查一个给定的属性是否能够被设置为 null

private NullabilityInfoContext _nullabilityContext = new NullabilityInfoContext();

private void DeserializePropertyValue(PropertyInfo p, object instance, object? value)
{
    if (value is null)
    {
        var nullabilityInfo = _nullabilityContext.Create(p);
        if (nullabilityInfo.WriteState is not NullabilityState.Nullable)
        {
            throw new MySerializerException($"Property '{p.GetType().Name}.{p.Name}'' cannot be set to null.");
        }
    }

    p.SetValue(instance, value);
}

获取嵌套的可空性信息

可空性对能够持有其余对象的对象有特殊处理,好比数组和元组。例如,你能够指定一个数组对象(做为一个变量,或者做为一个类型成员签名的一部分)必须是非空的,可是元素能够是空的,或者相反。这种额外的特殊性是能够经过新的反射 API 来检查的,例如:

class Data
{
    public string?[] ArrayField;
    public (string?, object) TupleField;
}
private void Print()
{
    Type type = typeof(Data);
    FieldInfo arrayField = type.GetField("ArrayField");
    FieldInfo tupleField = type.GetField("TupleField");

    NullabilityInfoContext context = new ();

    NullabilityInfo arrayInfo = context.Create(arrayField);
    Console.WriteLine(arrayInfo.ReadState);        // NotNull
    Console.WriteLine(arrayInfo.Element.State);    // Nullable

    NullabilityInfo tupleInfo = context.Create(tupleField);
    Console.WriteLine(tupleInfo.ReadState);                      // NotNull
    Console.WriteLine(tupleInfo.GenericTypeArguments [0].State); // Nullable
    Console.WriteLine(tupleInfo.GenericTypeArguments [1].State); // NotNull
}

类库:ZipFile 遵循 Unix 文件权限

System.IO.Compression.ZipFile 类如今能够在建立过程当中捕获 Unix 文件权限,并在相似 Unix 的操做系统上提取压缩文件时设置文件权限。这一变化容许可执行文件在压缩包中被循环使用,这意味着你再也不须要修改文件权限来使文件在解压缩包后可执行。它也一样遵循 usergroupother 读/写权限。

若是一个压缩包不包含文件权限(由于它是在 Windows 上建立的,或者使用了一个没有捕获权限的工具,好比早期的 .NET 版本),那么解压缩的文件就会获得默认的文件权限,就像其余新建立的文件同样。

Unix 的文件权限也适用于其余压缩工具,包括:

  • Info-ZIP
  • 7-Zip

早期 .NET 7 功能预览:泛型 Math

对于 .NET 6,咱们已经创建了将 API 标记为“预览”的能力[14]。这种新方法将使咱们可以在多个主要版本中提供和发展预览功能。为了使用预览 API,项目须要明确选择使用的预览功能。若是你在没有明确选择的状况下使用预览功能,从 .NET 6 RC1 开始,你会看到带有可操做信息的构建错误。预览功能预计将在之后的版本中发生变化,可能会有破坏性的变化。这就是为何他们要选择加入。

咱们在 .NET 6 中预览的这些功能之一是静态抽象接口成员。这些容许你在接口中定义静态的抽象方法(包括操做符)。例如,如今能够实现代数泛型方法。对于一些人来讲,这个功能将是咱们今年交付的绝对突出的改进。这也许是自 Span<T> 以来最重要的新类型系统特性。

下面的例子是一个 IEnumerable<T>,因为 T 被限制为 INumber<T>,多是一个 INumber<int>,因此可以对全部的数值进行求和。

public static T Sum<T>(IEnumerable<T> values)
    where T : INumber<T>
{
    T result = T.Zero;

    foreach (var value in values)
    {
        result += value;
    }

    return result;
}

这是由于 INumber<T> 定义了各类(静态)操做符重载,必须由接口实现者来知足。IAdditionOperators 也许是最容易理解的新接口,INumber<T> 自己就是派生自这个接口。

这都是由一个新的功能提供的,它容许在接口中声明静态抽象成员。这使得接口能够公开运算符和其余静态方法,好比 ParseCreate,而且这些方法能够由派生类型实现。更多细节请参见咱们的相关博文[15]

全部提到的功能都是 .NET 6 的预览版,不支持在生产中使用。咱们将感谢您在使用中提供反馈。咱们打算在 .NET 7 中继续发展和改进泛型 Math 功能以及支持它们的运行时和 C# 功能。咱们但愿对当前的体验进行突破性的改变,这也是为何新的 API 被标记为“预览”的部分缘由。

类库:NativeMemory API

咱们增长了新的本地内存分配 API[16],经过 System.Runtime.InteropServices.NativeMemory 公开。这些 API 至关于 C 语言中的 mallocfreerealloccalloc API,还包括用于进行对齐分配的 API。

你可能想知道如何看待这些 API。首先,它们是低级别的 API,是为低级别的代码和算法准备的。应用程序开发人员不多会用到这些。另外一种思考这些 API 的方式相似于平台内部的 API,它们是用于 CPU 指令的低级别 .NET API。这些 API 是相似的,但它是为内存相关的操做暴露的低级别的 API。

类库:System.Text.Json 序列化通知

System.Text.Json 序列化器如今将通知做为(反)序列化操做的一部分公开。它们对于默认值和验证很是有用。要使用它们,请在 System.Text.Json.Serialization 命名空间中实现一个或多个接口 IJsonOnDeserializedIJsonOnDeserializingIJsonOnSerialized 或 IJsonOnSerializing`。

这里有一个例子,在 JsonSerializer.Serialize()JsonSerializer.Deserialize() 中都进行验证,以确保 FirstName 属性不是 null

public class Person : IJsonOnDeserialized, IJsonOnSerializing
{
    public string FirstName{ get; set; }

    void IJsonOnDeserialized.OnDeserialized() => Validate(); // Call after deserialization
    void IJsonOnSerializing.OnSerializing() => Validate(); // Call before serialization

    private void Validate()
    {
        if (FirstName is null)
        {
            throw new InvalidOperationException("The 'FirstName' property cannot be 'null'.");
        }
    }
}

之前,你须要实现一个自定义转换器来实现这一功能。

类库:System.Text.Json 序列化属性排序

咱们使用 System.Text.Json.Serialization.JsonPropertyOrderAttribute 特性增长了控制属性序列化顺序的能力,用一个整数指定了顺序,较小的整数先被序列化;没有该特性的属性有一个默认的排序值 0

这里有一个例子,指定 JSON 应该按照 Id, City, FirstName, LastName 的顺序进行序列化:

public class Person
{
    public string City { get; set; } // No order defined (has the default ordering value of 0)

    [JsonPropertyOrder(1)] // Serialize after other properties that have default ordering
    public string FirstName { get; set; }

    [JsonPropertyOrder(2)] // Serialize after FirstName
    public string LastName { get; set; }

    [JsonPropertyOrder(-1)] // Serialize before other properties that have default ordering
    public int Id { get; set; }
}

之前,序列化顺序是由反射顺序决定的,而反射顺序既不是肯定的,也不会致使特定的预期顺序。

类库:System.Text.Json.Utf8JsonWriter

在用 Utf8JsonWriter 编写 JSON payloads 时,有时你须要嵌入“原始”JSON。

好比:

  • 我有一个设计好的字节序列,以下例所示。
  • 我有一个 blob,我认为它表明 JSON 内容,我想把它包起来,我须要确保包和它的内部保持良好的格式。
JsonWriterOptions writerOptions = new() { WriteIndented = true, };

using MemoryStream ms = new();
using UtfJsonWriter writer = new(ms, writerOptions);

writer.WriteStartObject();
writer.WriteString("dataType", "CalculationResults");

writer.WriteStartArray("data");

foreach (CalculationResult result in results)
{
    writer.WriteStartObject();
    writer.WriteString("measurement", result.Measurement);

    writer.WritePropertyName("value");
    // Write raw JSON numeric value using FormatNumberValue (not defined in the example)
    byte[] formattedValue = FormatNumberValue(result.Value);
    writer.WriteRawValue(formattedValue, skipValidation: true);

    writer.WriteEndObject();
}

writer.WriteEndArray();
writer.WriteEndObject();

如下是对上述代码--特别是FormatNumberValue--的描述。为了提升性能,System.Text.Json 在数字为整数时省略了小数点/值,如 1.0。其理由是,写的字节数越少越好,有利于提升性能。在某些状况下,保留小数点可能很重要,由于消费者将没有小数点的数字视为整数,不然视为浮点数。这种新的“原始值”模型容许你在任何须要的地方拥有这种程度的控制。

类库:JsonSerializer 同步流重载

咱们为 JsonSerializer 添加了新的同步 API[17],用于将 JSON 数据序列化和反序列化到一个流。你能够在下面的例子中看到这个演示。

using MemoryStream ms = GetMyStream();
MyPoco poco = JsonSerializer.Deserialize<MyPoco>(ms);

这些新的同步 API 包括与新的 System.Text.Json source generator[18] 兼容和可用的重载,经过接受 JsonTypeInfo<T>JsonSerializerContext 实例。

类库:System.Diagnostics Propagators

在过去的几年里,咱们一直在改进对 OpenTelemetry[19] 的支持。实现该支持的一个关键点是确保全部须要参与遥测生产的组件以正确的格式输出到网络头。要作到这一点真的很难,特别是随着 OpenTelemetry 规范的变化。OpenTelemetry 定义了传播(propagation)[20]的概念来帮助解决这种状况。咱们正在采用传播的方式来实现头的定制的通常模型。

关于更普遍的概念背景:

  • OpenTelemetry 规范 - 分布式跟踪数据结构的内存表示。
  • OpenTelemetry Span - 追踪构建块,在 .NET 中由 System.Diagnostics.Activity 表示。
  • W3C TraceContext - 关于如何经过众所周知的 HTTP 头传播这些分布式跟踪数据结构的规范。

下面的代码演示了使用传播的通常方法:

DistributedContextPropagator propagator = DistributedContextPropagator.Current;
propagator.Inject(activity, carrier, (object theCarrier, string fieldName, string value) =>
{
   // Extract the context from the activity then inject it to the carrier.
});

你也能够选择使用不一样的传播器(propagator):

// Set the current propagation behavior to not transmit any distributed context information in outbound network messages.
DistributedContextPropagator.Current = DistributedContextPropagator.CreateNoOutputPropagator();

DistributedContextPropagator 抽象类决定了分布式上下文信息在网络传输时是否以及如何被编码和解码。编码能够经过任何支持字符串键/值对的网络协议进行传输。DistributedContextPropagator 以字符串键/值对的形式向载体注入数值并从载体中提取数值。经过添加对传播者的支持,咱们实现了两件事。

  • 你再也不须要使用 W3C 的 TraceContext 头文件。你能够编写一个自定义的传播器(甚至用你本身的头文件名称),而不须要 HttpClient、ASP.NET Core 等库对这种自定义格式有预先的了解。
  • 若是你实现了一个带有自定义传输的库(如消息队列),只要你支持发送和接收文本映射(如 Dictionary<string, string>),你如今能够支持各类格式。

大多数应用程序代码不须要直接使用这个功能,然而,若是你使用 OpenTelemetry,你极可能会在调用栈中看到它。一些库的代码若是关心跟踪和因果关系,可能会须要使用这个模型。

类库:加密操做调用模式简化

.NET 的加密和解密部件是围绕着流设计的,没有真正的概念来定义何时有效载荷已经在内存中(already in memory)。SymmetricAlgorithm 上新的 Encrypt-Decrypt- 方法加速了 already in memory 的进展,目的是为调用者和代码审查者提供清晰的信息。此外,它们还支持从 span 中读取和写入。

新的简化方法为使用加密 API 提供了一个直接的方法:

private static byte[] Decrypt(byte[] key, byte[] iv, byte[] ciphertext)
{
    using (Aes aes = Aes.Create())
    {
        aes.Key = key;

        return aes.DecryptCbc(ciphertext, iv);
    }
}

在新的 Encrypt-Decrypt- 方法中,只使用 SymmetricAlgorithm 实例中的 Key 属性。新的 DecryptCbc 方法支持选择填充算法,可是 PKCS#7 常常与 CBC 一块儿使用,因此它是一个默认参数。若是你喜欢这种清晰的感受,就指定它吧:

private static byte[] Decrypt(byte[] key, byte[] iv, byte[] ciphertext)
{
    using (Aes aes = Aes.Create())
    {
        aes.Key = key;

        return aes.DecryptCbc(ciphertext, iv, PaddingMode.PKCS7);
    }
}

你能够看到,现有的模式--使用 .NET 5--明显须要更多的管道来实现一样的结果:

private static byte[] Decrypt(byte[] key, byte[] iv, byte[] ciphertext)
{
    using (Aes aes = Aes.Create())
    {
        aes.Key = key;
        aes.IV = iv;

        // These are the defaults, but let's set them anyways.
        aes.Padding = PaddingMode.PKCS7;
        aes.Mode = CipherMode.CBC;

        using (MemoryStream destination = new MemoryStream())
        using (ICryptoTransform transform = aes.CreateDecryptor())
        using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
        {
            cryptoStream.Write(ciphertext, 0, ciphertext.Length);
            cryptoStream.FlushFinalBlock();
            return destination.ToArray();
        }
    }
}

运行时:支持全部平台和架构的 W^X

运行时如今有一种模式,它不建立或使用任何同时可写和可执行的内存页。全部可执行的内存都被映射为只读不执行。这项功能在该版本的早期仅在 macOS 上启用--针对 Apple Silicon。在 Apple Silicon 机器上,禁止同时进行可写和可执行的内存映射。

这一功能如今在全部其余平台上被启用并支持。在这些平台上,可执行代码的生成/修改是经过单独的读写内存映射完成的,这对 JIT 代码和运行时生成的辅助程序都是如此。这些映射是在与可执行代码地址不一样的虚拟内存地址上建立的,而且只在进行写入时存在很是短暂的时间。例如,JIT 如今生成代码到一个从头开始的缓冲区,在整个方法被 jitted 后,使用一个内存拷贝函数调用将其复制到可执行内存中。而可写映射的寿命只跨越了内存拷贝的时间。

这个新功能能够经过设置环境变量 DOTNET_EnableWriteXorExecute 为 1 来启用。这个功能在 .NET 6 中是可选的,由于它有一个启动时的退步(除了在 Apple Silicon 上)。在咱们的 ASP.NET 基准测试中,当用 Ready To Run(R2R)编译时,退步了 ~10%。然而,在启用和未启用该功能的状况下,测得的稳态性能是同样的。对于启动性能并不重要的应用程序,咱们建议启用该功能,由于它能提升安全性。咱们打算做为 .NET 7 的一部分解决性能退步问题,届时默认启用该功能。

结束

咱们认为,咱们已经到了新功能和改进已经完成的发布点。为团队点赞!

咱们继续期待大家的反馈。咱们将把 .NET 6 的其他部分放在完善(功能和性能)和新功能中发现的错误上。在大多数状况下,功能改进须要等到 .NET 7。请分享你的任何反馈,咱们将很高兴对其进行分类。

感谢全部为 .NET 6 作出贡献的人,使其成为另外一个伟大的版本。

感谢你成为一名 .NET 开发者。

文中相关连接:

[1].https://dotnet.microsoft.com/
[2].https://dotnet.microsoft.com/download/dotnet/6.0
[3].https://devblogs.microsoft.com/dotnet/announcing-net-maui-preview-7/
[4].https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-7/
[5].https://devblogs.microsoft.com/dotnet/category/conversations/
[6].https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/
[7].https://docs.microsoft.com/dotnet/core/compatibility/sdk/6.0/csharp-template-code
[8].https://docs.microsoft.com/dotnet/core/compatibility/sdk/6.0/implicit-namespaces
[9].https://docs.microsoft.com/dotnet/csharp/nullable-references
[10].https://github.com/dotnet/runtime/issues/29723
[11].https://twitter.com/JeffHandley/status/1424846146850131968
[12].https://github.com/jellyfin/jellyfin/blob/c07e83fdf87e61f30e4cca4e458113ac315918ae/Directory.Build.props#L5
[13].https://github.com/dotnet/roslyn/blob/main/docs/features/nullable-metadata.md
[14].https://github.com/dotnet/designs/blob/main/accepted/2021/preview-features/preview-features.md
[15].https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/
[16].https://github.com/dotnet/runtime/pull/54006
[17].https://github.com/dotnet/runtime/issues/1574
[18].https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/
[19].https://devblogs.microsoft.com/dotnet/opentelemetry-net-reaches-v1-0/
[20].https://opentelemetry.lightstep.com/core-concepts/context-propagation/
相关文章
相关标签/搜索