【已更新最新开发文章,点击查看详细】

C# 9.0 向 C# 语言添加了以下功能和增强功能:

  • 记录
  • 仅限 Init 的资源库
  • 顶级语句
  • 模式匹配增强功能
  • 本机大小的整数
  • 函数指针
  • 禁止发出 localsinit 标志
  • 目标类型的新表达式
  • 静态匿名函数
  • 目标类型的条件表达式
  • 协变返回类型
  • 扩展 GetEnumerator 支持 foreach 循环
  • Lambda 弃元参数
  • 本地函数的属性
  • 模块初始值设定项
  • 分部方法的新功能

C# 语言版本控制。

1、记录类型

默认情况下,记录是不可变的。

值类型变量可保存值,因此在将值类型传递给方法时,会对原始数据的副本进行更改。

请考虑以下定义:

public record Person
{
    public string LastName { get; }
    public string FirstName { get; }

    public Person(string first, string last) => (FirstName, LastName) = (first, last);
}

定义记录类型时,编译器会合成其他几种方法:

  • 基于值的相等性比较方法
  • GetHashCode()
  • 复制和克隆成员
  • ToString()

可声明派生自 Person 的新记录,如下所示:

public record Teacher : Person
{
    public string Subject { get; }

    public Teacher(string first, string last, string sub)
        : base(first, last) => Subject = sub;
}

还可密封记录以防止进一步派生:

public sealed record Student : Person
{
    public int Level { get; }

    public Student(string first, string last, int level) : base(first, last) => Level = level;
}

记录应具有以下功能:

  • 例如,即使两条记录的名称相同,Student 也不能等于 Person
  • 记录具有为你生成的一致的字符串表示形式。
  • 正确的副本构造必须包括继承层次结构和开发人员添加的属性。
  • 这些复制和修改操作支持非破坏性转变。

两条记录的类型必须匹配,而且记录类型之间共享的所有属性也必须相等。

编译器根据 record 上的访问修饰符为“克隆”方法添加不同的修饰符:

  • 如果基类型不是 object,则方法也是 override
  • 当基类型为 object 时,对于不是 abstract 的记录类型:
    • 如果记录为 sealed,则不向“克隆”方法添加其他修饰符(这意味着它不是 virtual)。
    • 如果记录不是 sealed,则“克隆”方法为 virtual
  • 当基类型不是 object 时,对于不是 abstract 的记录类型:
    • 如果记录是 sealed,则“克隆”方法也是 sealed
    • 如果记录不是 sealed,则“克隆”方法为 override

如果两条记录的属性相等且类型相同,则它们彼此相等,如下例所示:

var person = new Person("Bill", "Wagner");
var student = new Student("Bill", "Wagner", 11);

Console.WriteLine(student == person); // false

ToString() 方法返回一个 string,类似于以下代码:

"Student { LastName = Wagner, FirstName = Bill, Level = 11 }"

下面是先前定义为位置记录的 3 种记录类型:

public record Person(string FirstName, string LastName);

public record Teacher(string FirstName, string LastName,
    string Subject)
    : Person(FirstName, LastName);

public sealed record Student(string FirstName,
    string LastName, int Level)
    : Person(FirstName, LastName);

可添加正文,还可包括其他任何方法:

public record Pet(string Name)
{
    public void ShredTheFurniture() =>
        Console.WriteLine("Shredding furniture");
}

public record Dog(string Name) : Pet(Name)
{
    public void WagTail() =>
        Console.WriteLine("It's tail wagging time");

    public override string ToString()
    {
        StringBuilder s = new();
        base.PrintMembers(s);
        return $"{s.ToString()} is a dog";
    }
}

Deconstruct 方法可用于将记录析构为其组件属性:

var person = new Person("Bill", "Wagner");

var (first, last) = person;
Console.WriteLine(first);
Console.WriteLine(last);

with 表达式指示编译器创建记录的副本,但修改了指定的属性:

Person brother = person with { FirstName = "Paul" };

ToString() 方法作为示例。

2、仅限 Init 的资源库

在完成所有初始化(包括属性初始化表达式和 with 表达式)之后,构造阶段实际上就结束了。

例如,以下结构定义了天气观察结构:

public struct WeatherObservation
{
    public DateTime RecordedAt { get; init; }
    public decimal TemperatureInCelsius { get; init; }
    public decimal PressureInMillibars { get; init; }

    public override string ToString() =>
        $"At {RecordedAt:h:mm tt} on {RecordedAt:M/d/yyyy}: " +
        $"Temp = {TemperatureInCelsius}, with {PressureInMillibars} pressure";
}

调用方可使用属性初始化表达式语法来设置值,同时仍保留不变性:

var now = new WeatherObservation 
{ 
    RecordedAt = DateTime.Now, 
    TemperatureInCelsius = 20, 
    PressureInMillibars = 998.0m 
};

但在初始化后更改观察值是错误的,它会在初始化之外分配给仅限 init 的属性:

// Error! CS8852.
now.TemperatureInCelsius = 18;

可为定义的任何 class 或 struct 声明仅限 init 的资源库。

3、顶级语句

程序:

using System;

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

借助顶级语句,可使用 using 语句和执行操作的一行替换所有样本:

using System;

Console.WriteLine("Hello World!");

如果需要单行程序,可删除 using 指令,并使用完全限定的类型名称:

System.Console.WriteLine("Hello World!");

从某种意义上讲,可认为一个文件包含通常位于 Program 类的 Main 方法中的语句。

Azure 函数是顶级语句的理想用例。

在这种情况下,合成入口点将返回 Task 或 Task<int>

4、模式匹配增强功能

C# 9 包括新的模式匹配改进:

  • 类型模式要求在变量是一种类型时匹配
  • 带圆括号的模式强制或强调模式组合的优先级
  • 联合 and 模式要求两个模式都匹配
  • 析取 or 模式要求任一模式匹配
  • 求反 not 模式要求模式不匹配
  • 关系模式要求输入小于、大于、小于等于或大于等于给定常数。

请考虑下列示例:

public static bool IsLetter(this char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

还可使用可选的括号来明确 and 的优先级高于 or

public static bool IsLetterOrSeparator(this char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';

最常见的用途之一是用于 NULL 检查的新语法:

if (e is not null)
{
    // ...
}

这些模式中的任何一种都可在允许使用模式的任何上下文中使用:is 模式表达式、switch 表达式、嵌套模式以及 switch 语句的 case 标签的模式。

5、性能和互操作性

3 项新功能改进了对需要高性能的本机互操作性和低级别库的支持:本机大小的整数、函数指针和省略 localsinit 标志。

在广泛使用整数数学且需要尽可能快的性能的情况下,本机大小的整数可提高性能。

可使用 delegate* 声明中的属性来指定其他调用约定。

此属性不会影响 abstract 方法,它会影响为实现生成的代码。

其他功能需要不安全的代码。

6、调整和完成功能

最常见的用法是在字段声明中:

private List<WeatherObservation> _observations = new();

请考虑使用以下签名的 ForecastFor() 方法:

public WeatherForecast ForecastFor(DateTime forecastDate, WeatherForecastOptions options)

可按如下所示调用该方法:

var forecast = station.ForecastFor(DateTime.Now.AddDays(2), new());

此功能还有一个不错的用途是,将其与仅限 init 的属性组合使用来初始化新对象:

WeatherStation station = new() { Location = "Seattle, WA" };

可使用 return new(); 语句返回由默认构造函数创建的实例。

你会注意到,某些以前需要强制转换或无法编译的条件表达式现在可以正常工作。

static 修饰符可防止意外捕获其他变量。

这对于记录和其他支持虚拟克隆或工厂方法的类型很有用。

在设计中,应将其限制为在枚举对象有意义时使用。

Lambda 表达式的输入参数一节。

可为空的属性注释应用于本地函数。

7、支持代码生成器

典型的代码生成器会在代码中搜索属性或其他约定。

源生成器只能添加代码,不能修改编译中的任何现有代码。

如果分部方法包括 private 访问修饰符,则由新规则控制该分部方法。

模块初始化表达式方法:

  • 必须是静态的
  • 必须没有参数
  • 必须返回 void
  • 不能是泛型方法
  • 不能包含在泛型类中
  • 必须能够从包含模块访问

方法不能为本地函数。

 

https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-9#record-types

 

【已更新最新开发文章,点击查看详细】

相关文章: