首页 科技正文

欧博代理:【译】Welcome to C# 9.0

admin 科技 2020-06-01 29 0

  C# 9.0正在形成,我想分享我们对添加到该语言下个版本的一些主要功能的看法。对于每个新版本的 C#,我们努力使常见的编码方案加倍清晰和简朴,C# 9.0 也不破例。这次的一个稀奇重点是支持数据形状的简练和不能变示意。

  让我们潜入吧!

1 仅可初始化的属性

  工具初始化器是异常好用的。它们为类型实例化提供了一种异常天真且可读的花样来建立工具,尤其是对于一次建立稀奇大的嵌套工具来说。下面是一个简朴的例子:

new Person
{
    FirstName = "Scott",
    LastName = "Hunter"
}

  工具初始化也使用户不必编写大量组织函数,要做的就是编写一些属性!

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

  今天,一个很大的限制是,属性必须是可修改的,工具初始化器是这样事情的:首先挪用工具的组织函数(默以为无参的组织函数),然后分配给属性设置器(property setter)。

  仅可初始化属性修改了这一点!它们引入了一个 init 接见器,该接见器是set接见器的变体,只能在工具初始化时代挪用:

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

  使用此声明,除了初始化外,之后任何后续赋值给 FirstName 和 LastName 属性都是一个错误。

  由于init接见器只能在初始化时代接见,因此他们允许修改封锁类型中的只读字段,就像在组织函数中那样:

public class Person
{
    private readonly string firstName;
    private readonly string lastName;
   
    public string FirstName
    {
        get => firstName;
        init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
    }
    public string LastName
    {
        get => lastName;
        init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
    }
}

2 纪录

  若是要使单个属性不能变,则仅可初始化属性异常适合。若是希望整个工具不能变且像值类型一样,则应思量将其声明为纪录:

public data class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

  类声明中的data关键字将其标记为纪录。这赋予它几个类似价值类型的行为,我们将在下面深入探讨这些行为。一般来说,纪录更被视为"值"(纯数据), 而不是作为工具。您可以通过建立新纪录示意新状态来示意随时间的转变。它们不是由标识界说,而是由其内容界说。

2.1 With表达式

  使用不能变数据时,一种常见模式是从现有值建立新值以示意新状态。例如,若是我们更改LastName,我们会将其示意为一个新工具,该工具是旧工具的副本,但LastName差别。这种手艺通常被称为非破坏性修改。纪录这种特征示意的是Person在给定时间的状态。

  为了顺应这种编程气概,纪录允许一种新的表达式——with:

var otherPerson = person with{LastName="Hanselman"};

  with表达式使用工具初始化器语法来说明新工具与旧工具的差别内容。您可以指定多个属性。

  纪录隐式界说一个受珍爱的"复制组织函数"-一个组织函数,它获取现有纪录工具,并逐个将其字段复制到新的工具:

protected Person(Person original){/* copy all the fields */}// generated

  with 表达式会导致挪用复制组织函数,然后在上面应用工具初始化器以响应地更改属性。

  若是您不喜欢天生的复制组织函数的默认行为,则可以改为界说自己的行为,该行为将由with表达式选取。

2.2 基于值的相等性

  所有工具都从Object继续 Equals(object)。结构将其重写为具有"基于价值的相等性",通过递归地挪用Equals来对照结构的每个字段。纪录也执行相同的操作。这意味着,凭据其"值",两个纪录工具可以相互相等,而不必是统一工具。例如:

var originalPerson = otherPerson with { LastName = "Hunter" };

  现在 ReferenceEquals(person, originalPerson) = false(这两个不是一个工具)然则Equals(person, originalPerson) = true (他们有相同的值)。

  若是您不喜欢天生的 Equals 重写的默认逐字段对照行为,则可以改为编写自己的字段对照行为。你只需要小心,你领会基于值的相等在纪录中是若何事情的,稀奇是当涉及继续时。

  除了重写Equals 外,另有 GetHashCode()。

2.3 数据成员

  纪录绝大多数都是不能变的,只有只读初始化器可以通过with表达式举行非破坏性修改。为了针对这种常见情形举行优化,纪录在声明时会更改string FirstName这类成员声明的行为。与其他类和结构声明中的隐式private字段差别,在纪录中,这被视为public的、仅可初始化的自动属性的缩写!因此:

public data classPerson
{
    string FirstName;
    string LastName;
}

  与

public data classPerson
{
    public string FirstName{get; init;}
    public string LastName{get; init;}
}

  是相同的。

  我们以为这有助于做出漂亮而清晰的纪录声明。若是您真的需要私有字段,只需显式地添加private修饰符:

private string firstName;

2.4 基于位置的纪录

  有时,对纪录接纳更为位置化的方式是有用的,在这种方式中,纪录的内容通过组织函数参数的位置给出,而且可以通过解构函数来提取。

  可以在纪录中指定自己的组织函数和解构函数:

public data classPerson
{
    string FirstName;
    string LastName;
    public Person(string firstName,string lastName)
      =>(FirstName,LastName)=(firstName, lastName);
    public void Deconstruct(out string firstName,out string lastName)
      =>(firstName, lastName)=(FirstName,LastName);
}

  上面代码可以简写为:

public data class Person(string FirstName,string LastName);

  这将声明public的仅初始化的自动属性以及组织函数和解构函数,以便您可以编写:

var person =new Person("Scott","Hunter");// positional construction
var(f, l)= person;                        // positional deconstruction

  若是您不喜欢天生的自动属性,则可以改为界说自己的同名属性,天生的组织函数和解构函数将使用该属性。

2.5 纪录的改变引发的问题

  想象一下,将纪录工具放入字典中。再次找到它取决于 Equal 和GetHashCode。若是纪录改变其状态,它也会改变它即是什么!我们可能再也找不到了!在哈希表实现中,它甚至可能损坏数据结构,由于定位基于的是"到达哈希表时"的哈希值!

  虽然可以通过重写一些内部方式来改变这种默认的行为,但其事情量也是相当伟大的。

2.6 with表达式与继续

public data class Person{string FirstName;string LastName;}
public data class Student:Person{int ID;}
Person person =new Student{FirstName="Scott",LastName="Hunter", ID =GetNewId()};
otherPerson = person with{LastName="Hanselman"};

  在最后一行上使用with表达式时,编译器不知道person实际上包含了一个Student。而且,纵然otherPerson实际上不是"Student"工具,它也不是一个准确的副本,该工具与复制的第一个工具具有相同的ID。

  纪录有一个隐藏的虚方式,它委托"克隆"整个工具。每个派生纪录类型都重写此方式以挪用该类型的复制组织函数,以及派生链上的复制组织函数直到基类纪录的复制组织函数。with表达式只需挪用隐藏的"克隆"方式,并将工具初始化器应用于效果。

2.7 值相等与继续

  与with表达式的实现类似,基于值的相等性也必须是"虚拟"的,即Student需要对照所有字段,纵然对照时能够得知类型是基类型Person。这是很容易通过重写已经虚拟的Equals方式实现的。

  然则,相等另有一个挑战:若是对照两种差别的Person,该怎么办?我们不能让其中一个决议是否相等:相等应该是对称的,以是无论两个工具中哪个是第一个,效果都应该是相同的。换句话说,他们必须就适用的相等杀青一致!

  说明问题的示例:

Person person1 =new Person{FirstName="Scott",LastName="Hunter"};
Person person2 =new Student{FirstName="Scott",LastName="Hunter", ID =GetNewId()};

  这两个工具相互相等吗?person1可能会这样以为,由于person2有所有的Person的组织,但person2会以为与person1差别!我们需要确保他们都赞成他们是差别的工具。

  C# 会自动为您处置。它的实现方式是每个纪录都有一个"EqualityContract"的虚拟受珍爱属性。每个派生纪录都市重写它,为了对照相等,两个工具必须具有相同的EqualityContract。

3 简化顶级程序

  之前我们这样写代码:

using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}

  现在您可以选择在顶层编写主程序:

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

  支持任何语句,但必须在using之后以及文件中的任何类型或命名空间声明之前,而且只能在一个文件中执行此操作,就像现在只能有一个Main方式一样。若是要返回状态代码,可以执行此操作。若是你想await,你可以这样做。若是要接见命令行参数,可以接见args参数。

  局部函数是语句的一种形式,在顶级程序中也允许使用。从顶级语句部门以外的任何位置挪用它们都是错误的。

4 改善模式匹配

  在 C# 9.0 中添加了几种新类型的模式。例如:

public static decimal CalculateToll(object vehicle) =>
    vehicle switch
    {
       ...
        DeliveryTruck t when t.GrossWeightClass > 5000 => 10.00m + 5.00m,
        DeliveryTruck t when t.GrossWeightClass < 3000 => 10.00m - 2.00m,
        DeliveryTruck _ => 10.00m,
        _ => throw new ArgumentException("Not a known vehicle type", nameof(vehicle))
    };

4.1 简朴类型模式

  现在,类型模式需要在类型匹配时声明一个标识符,纵然该标识符是一个_,好比 DeliveryTruck  _。新语法不用了,可以简写为:

DeliveryTruck => 10.00m,

4.2 关系模式

  C#9.0引入了对应于关系运算符<、<=等的模式。因此,新语法可以这样写:

DeliveryTruck t when t.GrossWeightClass switch
{
    > 5000 => 10.00m + 5.00m,
    < 3000 => 10.00m - 2.00m,
    ...
},

4.3 逻辑模式

  最后,可以将模式与逻辑运算(and 、or、not)符组合起来,并将其拼写为单词,以避免与表达式中使用的运算符混淆。例如:

DeliveryTruck t when t.GrossWeightClass switch
{
    < 3000 => 10.00m - 2.00m,
    >= 3000 and <= 5000 => 10.00m,
    > 5000 => 10.00m + 5.00m,
},

  not的常见用法是将其应用于判空。例如:

not null => throw new ArgumentException($"Not a known vehicle type: {vehicle}", nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))

  另有,if (!(e is Customer)) { ... }在新语法中,可以写为if (e is not Customer) { ... }

5 目的类型

  "Target typing"是当表达式从使用位置的上下文中获取其类型时,我们使用的术语。C# 9.0支持新的类型推断。

5.1 new

  新语法中,若是是明确的类型,则在使用new时,可以不声明类型了。好比:

Point p = new (3, 5);

5.2 ?? and ?:

  现在,??与?:若是分支之间不是统一类型会报错。新语法下,若是两个分支都可以转换为目的类型则是允许的:

Person person = student ?? customer; // Shared base type
int? result = b ? 0 : null; // nullable value type

6 改善协变

  有时,派生类中的方式返还比基类中的声明更详细的类型是很有用的。C# 9.0 允许:

abstract class Animal
{
    public abstract Food GetFood();
    ...
}
class Tiger : Animal
{
    public override Meat GetFood() => ...;
}

   此外,还要许多新的改善,让我们拭目以待吧。

原文链接

    https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/?utm_source=vs_developer_news&utm_medium=referral

,

欧博网址

www.cx11yx.cn欢迎进入欧博网址(Allbet Gaming),欧博网址开放会员注册、代理开户、电脑客户端下载、苹果安卓下载等业务。

版权声明

本文仅代表作者观点,
不代表本站Allbet的立场。
本文系作者授权发表,未经许可,不得转载。

评论