C # 9.0的record
官方消息: 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的更多相关文章
- jfinal对象封装Record原理
/*DbPro.class*/ public transient Record findFirst(String sql, Object paras[]{ List result = find(sql ...
- PL/0与Pascal-S编译器程序详细注释
学校编译课的作业之一,要求阅读两个较为简单的编译器的代码并做注释, 个人感觉是一次挺有意义的锻炼, 将自己的心得分享出来与一同在进步的同学们分享. 今后有时间再做进一步的更新和总结,其中可能有不少错误 ...
- 重开Vue2.0
目录: 内容: 一.Vue内部指令: 1.v-if v-else&v-show v-if与v-show都是选择性显示内容的指令,但是二者之间有区别: 1.v-if:判断是否加载,在需要的时候加 ...
- Eclipse 导入Hadoop 2.6.0 源码
1. 首先前往 官网(Hadoop 2.6 下载地址)上下载Hadoop的源码文件,并解压 2. 事先请确定已经安装好jdk以及maven(Maven安装教程 这是其他人写的一篇博文,保存profil ...
- 【初学python】使用python连接mysql数据查询结果并显示
因为测试工作经常需要与后台数据库进行数据比较和统计,所以采用python编写连接数据库脚本方便测试,提高工作效率,脚本如下(python连接mysql需要引入第三方库MySQLdb,百度下载安装) # ...
- layer——源码学习
一.根据源码的学习 发现创建弹窗:使用了一些div来组成 zindex 和 index 是自动生成. zindex 表示生成的层次关系 index 用来表示各个层的id 默认class名 h = [& ...
- 多功能弹窗控件layer
开发网站的时候,如何合理运用好各种插件对开发的帮助是很大的. 免去了我们调试各种交互效果, 比如常用的弹窗.气泡.提示.加载.焦点.标签.导航.折叠等等 这里会推荐几个常用的js插件,丰富多样简单易移 ...
- 使用Python和Perl绘制北京跑步地图
当你在一个城市,穿越大街小巷,跑步跑了几千公里之后,一个显而易见的想法是,如果能把在这个城市的所有路线全部画出来,会是怎样的景象呢? 文章代码比较多,为了不吊人胃口,先看看最终效果,上到北七家,下到南 ...
- JSP分页显示实例(基于Bootstrap)
首先介绍一款简单利落的分页显示利器:bootstrap-paginator 效果截图: GitHub官方下载地址:https://github.com/lyonlai/bootstrap-pagina ...
随机推荐
- Could not open ServletContext resource [/WEB-INF/applicationContext.xml] 解决办法
Spring官方文档中规定,如果在上下文中没有指定contextConfigLoction配置文件的位置,则会默认去WEB-INF中去寻找对应的配置文件. 理论上classpath的默认路径是WEB- ...
- MongoDb学习(五)--Gridfs--上传下载
版本 <dependency> <groupId>org.springframework.data</groupId> <artifactId>spri ...
- 无延时去斗按键实现方法(不好CPU)
这一灵感来源于定时器计数的方法,最后可以实现的效果跟咱们电脑键盘按键的效果一样!我先来介绍下基本原理吧! 采用定时器中断的方法,比如定时器终端我们设置为5ms,我们需要按键按下超过40ms时才算有按键 ...
- 使用ThreadLocal
使用ThreadLocal 阅读: 135212 多线程是Java实现多任务的基础,Thread对象代表一个线程,我们可以在代码中调用Thread.currentThread()获取当前线程.例如,打 ...
- Linux 时间同步 02 ntpd、ntpdate的区别
Linux 时间同步 02 ntpd.ntpdate的区别 目录 Linux 时间同步 02 ntpd.ntpdate的区别 [一]这样做不安全. [二]这样做不精确. [三]这样做不够优雅. ntp ...
- Spring—SSJ集成&声明式事务管理
1. 课程介绍 1. SSJ集成;(掌握) 2. 声明式事务管理;(掌握) 什么是三大框架 2.1. ssh Struts/Struts2 Spring Hibernate 2.2. ss ...
- .net通过iTextSharp.pdf操作pdf文件实现查找关键字签字盖章
之前这个事情都CA公司去做的,现在给客户做demo,要模拟一下签字盖章了,我们的业务PDF文件是动态生成的所以没法通过坐标定位,只能通过关键字查找定位了. 之前在网上看了许多通多通过查询关键字,然后图 ...
- php利用腾讯ip分享计划获取地理位置示例分享
<?php function getIPLoc_QQ($queryIP){ $url = 'http://ip.qq.com/cgi-bin/searchip?searchip1='.$quer ...
- Go GRPC 入门(二)
前言 最近较忙,其实准备一篇搞定的 中途有事,只能隔了一天再写 正文 pb.go 需要注意的是,在本个 demo 中,客户端与服务端都是 Golang,所以在客户端与服务端都公用一个 pb.go 模板 ...
- Laya Ts 简易对象池
ts版本的简易对象池 ,目前主要支持3D的物体,也可以将其改成其他类型 要使用首先调用InitPool 方法 `/* 使用说明: 使用必须先调用 InitPool 方法将对象池初始化 然后 Deque ...