官方消息: c # 9.0已经过时了!早在五月份,我就在博客中介绍了 c # 9.0计划,下面是该文章的更新版本,以便与我们最终发布的计划相匹配。

对于每一个新的 c # 版本,我们都在努力提高常见编码场景的清晰度和简单性,c # 9.0也不例外。这次的一个特别重点是支持数据形状的简洁和不可变的表示。

Init-only properties

对象初始化器非常棒。它们为类型的客户端创建对象提供了一种非常灵活和可读的格式,特别适用于嵌套对象创建,在嵌套对象创建过程中,可以一次性创建整个对象树。下面是一个简单的例子:

var person = new Person { FirstName = "Mads", LastName = "Torgersen" };

对象初始值设定项还可以使类型作者免于编写大量构造样板文件——他们所要做的就是编写一些属性!

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

目前的一个重大限制是,属性必须是可变的,对象初始化器才能工作: 它们的工作方式是首先调用对象的构造函数(本例中是缺省的、无参数的构造函数) ,然后分配给属性设置器。只有 init 属性可以解决这个问题!它们引入了一个 init 访问器,这是 set 访问器的一个变体,只能在对象初始化期间调用:

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

通过这个声明,上面的客户端代码仍然是合法的,但是任何后续对 FirstName 和 LastName 属性的赋值都是错误的:

var person = new Person { FirstName = "Mads", LastName = "Nielsen" }; // OK
person.LastName = "Torgersen"; // ERROR!

因此,只初始化属性保护对象的状态在初始化完成后不会发生变异。

Init accessors and readonly fields Init (访问器和只读字段)

因为只能在初始化期间调用 init 访问器,所以允许它们改变封闭类的只读字段,就像在构造函数中一样。

public class Person
{
private readonly string firstName = "<unknown>";
private readonly string lastName = "<unknown>"; 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)));
}
}

Records

经典面向对象程序设计的核心思想是,对象具有强大的身份,并封装了随时间演变的可变状态。C # 在这方面一直很有效,但是有时候你想要的恰恰相反,这里 c # 的默认设置往往会妨碍工作,让事情变得非常艰难。

如果你发现自己希望整个对象是不可变的,并且表现得像一个值,那么你应该考虑将它声明为一个记录:

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

记录仍然是一个类,但是记录关键字为它灌输了一些附加的类值行为。一般来说,记录是由它们的内容而不是它们的身份来定义的。在这方面,记录更接近于结构,但记录仍然是引用类型。

虽然记录是可变的,但是它们主要是为了更好地支持不可变数据模型而构建的。

With-expressions

在处理不可变数据时,一个常见的模式是从现有数据创建新的值来表示新的状态。例如,如果我们的人要改变他们的姓氏,我们会将其表示为一个新对象,这个对象是旧对象的副本,只是姓氏不同。这种技术通常被称为非破坏性突变。记录代表的不是随着时间的推移而代表的人,而是代表人在给定时间的状态。为了帮助这种编程风格,记录允许一种新的表达式: with-expression:

var person = new Person { FirstName = "Mads", LastName = "Nielsen" };
var otherPerson = person with { LastName = "Torgersen" };

With-expressions 使用对象初始值设定项语法来说明新对象与旧对象的不同之处。可以指定多个属性。

With-expression 的工作方式是将旧对象的完整状态复制到新对象中,然后根据对象初始值设定项对其进行变异。这意味着属性必须有一个 init 或 set 访问器才能在 with-表达式中更改。

Value-based equality

所有对象都从对象类继承一个虚 Equals (对象)方法。这被用作 Object 的基础。当两个参数都非空时,Equals (object,object)静态方法。结构会重写这个函数,使其具有“基于值的相等性”,并通过递归地调用 Equals 对结构的每个字段进行比较。唱片也是如此。这意味着,根据它们的“值性”,两个记录对象可以相等而不是同一个对象。例如,如果我们再次修改被修改人的姓氏:

我们现在有 ReferenceEquals (person,originalPerson) = false (它们不是同一个对象) ,但 Equals (person,originalPerson) = true (它们具有相同的值)。除了基于价值的 Equals 之外,还有一个基于价值的 GetHashCode ()覆盖。此外,记录实现了 IEquatable < t > 并使 = = 和!= 操作符,因此基于价值的行为在所有这些不同的平等机制中一致地显示出来。

价值等同性和易变性并不总能很好地结合在一起。一个问题是,更改值可能会导致 GetHashCode 的结果随着时间的推移而更改,如果对象存储在哈希表中,这将是不幸的!我们不禁止可变记录,但是我们不鼓励它们,除非您已经考虑到了后果!

Inheritance (继承)

记录可以从其他记录继承:

public record Student : Person
{
public int ID;
}

使用-表达式和值相等可以很好地处理记录继承,因为它们考虑了整个运行时对象,而不仅仅是静态地知道它的类型。假设我创建了一个 Student,但是把它存储在一个 Person 变量中:

Person student = new Student { FirstName = "Mads", LastName = "Nielsen", ID = 129 };

一个 with-expression 仍然会复制整个对象并保持运行时类型:

var otherStudent = student with { LastName = "Torgersen" };
WriteLine(otherStudent is Student); // true

以相同的方式,值相等确保两个对象具有相同的运行时类型,然后比较它们的所有状态:

Person similarStudent = new Student { FirstName = "Mads", LastName = "Nielsen", ID = 130 };
WriteLine(student != similarStudent); //true, since ID's are different

Positional records 位置记录

有时,对记录使用更加位置化的方法是有用的,其中记录的内容通过构造函数参数给出,并且可以通过位置解构提取。完全可以在一个记录中指定自己的构造函数和解构函数:

public record Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public Person(string firstName, string lastName)
=> (FirstName, LastName) = (firstName, lastName);
public void Deconstruct(out string firstName, out string lastName)
=> (firstName, lastName) = (FirstName, LastName);
}

但是对于表达完全相同的东西(参数名称的模大小写) ,有一个更短的语法:

public record Person(string FirstName, string LastName);

这声明了公共 init-only auto-properties、构造函数和解构函数,以便您可以编写:

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

如果不喜欢生成的 auto-property,可以改为定义自己的同名属性,生成的构造函数和解构函数将只使用该属性。在这种情况下,您可以使用该参数进行初始化。比如说,你希望 FirstName 是一个受保护的属性:

public record Person(string FirstName, string LastName)
{
protected string FirstName { get; init; } = FirstName;
}

位置记录可以这样调用基构造函数:

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

Top-level programs

用 c # 编写一个简单的程序需要大量的样板代码:

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

这不仅对语言初学者来说是压倒性的,而且会使代码变得杂乱无章,增加缩进的级别。在 c # 9.0中,你只需要在顶层编写你的主程序:

using System;

Console.WriteLine("Hello World!");

任何声明都是允许的。程序必须在使用之后以及文件中的任何类型或名称空间声明之前执行,而且只能在一个文件中执行此操作,就像现在只能有一个 Main 方法一样。如果您想返回状态代码,您可以这样做。如果你想等待,你可以这样做。如果您想访问命令行参数,可以使用 args 作为“ magic”参数。

using static System.Console;
using System.Threading.Tasks; WriteLine(args[0]);
await Task.Delay(1000);
return 0;

局部函数是一种语句形式,在顶级程序中也是允许的。从顶级语句部分以外的任何地方调用它们都是错误的。

Improved pattern matching

在 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))
};

Simple type patterns

以前,当类型匹配时,类型模式需要声明一个标识符——即使该标识符是一个丢弃的 _,如上面的 DeliveryTruck _ 中所示。但是现在你可以只写类型:

DeliveryTruck => 10.00m,

Relational patterns 关系模式

C # 9.0引入了与关系运算符 < 、 < = 等对应的模式。所以你现在可以把上面模式的 DeliveryTruck 部分写成一个嵌套的开关表达式:

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

这里 > 5000和 < 3000是关系模式。

Logical patterns

最后,您可以将模式与逻辑运算符组合起来,并且(或者)作为单词拼写,以避免与表达式中使用的运算符混淆。例如,上面的嵌套开关可以按如下升序排列:

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

这里的中间格使用和结合两个关系模式,并形成一个表示区间的模式。Not 模式的一个常见用法是将其应用于 null 常量模式,如 not null。例如,我们可以根据未知情况是否为空来分割处理:

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

在 if-conditions 中包含 is-expressions,而不是笨拙的双括号,这样也不方便:

if (!(e is Customer)) { ... }

你可以直接说

if (e is not Customer) { ... }

事实上,在 is not 这样的表达式中,我们允许您为 Customer 命名以供后续使用:

if (e is not Customer c) { throw ... } // if this branch throws or returns...
var n = c.FirstName; // ... c is definitely assigned here

Target-typed

“ Target typing”是一个术语,用于表达式从使用它的上下文中获取其类型。例如,null 和 lambda 表达式总是目标类型的。

C # 中的新表达式总是要求指定一个类型(隐式类型数组表达式除外)。在 c # 9.0中,如果表达式被赋值为一个明确的类型,则可以省略该类型。

Point p = new (3, 5);

当你有很多重复的时候,比如在数组或者对象初始值设定项中,这个特别好:

Point[] ps = { new (1, 2), new (5, 2), new (5, -3), new (1, -3) };

Covariant returns

有时表示派生类中的重写方法具有比基类中的声明更具体的返回类型是有用的。9.0允许:

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

还有更多…

查看完整的 c # 9.0特性的最佳位置是“ c # 9.0的新功能”文档页面。

C # 9.0的record的更多相关文章

  1. jfinal对象封装Record原理

    /*DbPro.class*/ public transient Record findFirst(String sql, Object paras[]{ List result = find(sql ...

  2. PL/0与Pascal-S编译器程序详细注释

    学校编译课的作业之一,要求阅读两个较为简单的编译器的代码并做注释, 个人感觉是一次挺有意义的锻炼, 将自己的心得分享出来与一同在进步的同学们分享. 今后有时间再做进一步的更新和总结,其中可能有不少错误 ...

  3. 重开Vue2.0

    目录: 内容: 一.Vue内部指令: 1.v-if v-else&v-show v-if与v-show都是选择性显示内容的指令,但是二者之间有区别: 1.v-if:判断是否加载,在需要的时候加 ...

  4. Eclipse 导入Hadoop 2.6.0 源码

    1. 首先前往 官网(Hadoop 2.6 下载地址)上下载Hadoop的源码文件,并解压 2. 事先请确定已经安装好jdk以及maven(Maven安装教程 这是其他人写的一篇博文,保存profil ...

  5. 【初学python】使用python连接mysql数据查询结果并显示

    因为测试工作经常需要与后台数据库进行数据比较和统计,所以采用python编写连接数据库脚本方便测试,提高工作效率,脚本如下(python连接mysql需要引入第三方库MySQLdb,百度下载安装) # ...

  6. layer——源码学习

    一.根据源码的学习 发现创建弹窗:使用了一些div来组成 zindex 和 index 是自动生成. zindex 表示生成的层次关系 index 用来表示各个层的id 默认class名 h = [& ...

  7. 多功能弹窗控件layer

    开发网站的时候,如何合理运用好各种插件对开发的帮助是很大的. 免去了我们调试各种交互效果, 比如常用的弹窗.气泡.提示.加载.焦点.标签.导航.折叠等等 这里会推荐几个常用的js插件,丰富多样简单易移 ...

  8. 使用Python和Perl绘制北京跑步地图

    当你在一个城市,穿越大街小巷,跑步跑了几千公里之后,一个显而易见的想法是,如果能把在这个城市的所有路线全部画出来,会是怎样的景象呢? 文章代码比较多,为了不吊人胃口,先看看最终效果,上到北七家,下到南 ...

  9. JSP分页显示实例(基于Bootstrap)

    首先介绍一款简单利落的分页显示利器:bootstrap-paginator 效果截图: GitHub官方下载地址:https://github.com/lyonlai/bootstrap-pagina ...

随机推荐

  1. 开发规范(一) 如何记录日志 By 阿里

  2. vue3系列:vue3.0自定义弹框组件V3Popup|vue3.x手机端弹框组件

    基于Vue3.0开发的轻量级手机端弹框组件V3Popup. 之前有分享一个vue2.x移动端弹框组件,今天给大家带来的是Vue3实现自定义弹框组件. V3Popup 基于vue3.x实现的移动端弹出框 ...

  3. 发起一个开源项目:基于 .NET 的博客引擎 fluss

    今天我们发起一个开源项目,它的名字叫 fluss,fluss 是 river 的德语. 百川归海,每一个博客就如一条河流,输入的是文字,流出的是知识,汇入的是知识的汪洋大海. 川流不息,fluss 是 ...

  4. 1001 害死人不偿命的(3n+1)猜想 (15分)

    卡拉兹(Callatz)猜想: 对任何一个正整数 n,如果它是偶数,那么把它砍掉一半:如果它是奇数,那么把 (3n+1) 砍掉一半.这样一直反复砍下去,最后一定在某一步得到 n=1.卡拉兹在 1950 ...

  5. kafka的概念

    1.生产者: 生产者发送消息到broker,有三种确认方式(request.required.acks)acks = 0: producer不会等待broker(leader)发送ack .因为发送消 ...

  6. Spring Security OAuth2.0认证授权一:框架搭建和认证测试

    一.OAuth2.0介绍 OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不 需要将用户名和密码提供给第三方应用或分享他们数据的所有内容. 1.s ...

  7. Solon rpc 之 SocketD 协议 - 消息应答模式

    Solon rpc 之 SocketD 协议系列 Solon rpc 之 SocketD 协议 - 概述 Solon rpc 之 SocketD 协议 - 消息上报模式 Solon rpc 之 Soc ...

  8. 这么优雅的Java ORM没见过吧!

      Java的ORM框架有很多,但由于Java语言的限制大部分都不够优雅也不够简单,所以作者只能另辟蹊径造轮子了.照旧先看示例代码了解个大概,然后再解释实现原理. 一.ORM示例 1. Insert ...

  9. [RTMP] 国内各大视频直播CDN厂商推流抢流行为分析

    背景 当存在一个推流客户端正在向rtmp://xxx.com/live/yyy推流时,又有另外一个推流客户端同时对这个地址进行推流,会发生什么呢? 查阅了 Adobe RTMP Spec 发现规范本身 ...

  10. 【Java基础】Java10 新特性

    Java10 新特性 局部变量类型推断 局部变量的显示类型声明,常常被认为是不必须的. 场景一:类实例化时.在声明一个变量时,总是习惯了敲打两次变量类型,第一次用于声明变量类型,第二次用于构造器. 场 ...