C#9特性整理(部分)

1. 实例化类型推断(Target-typed new)

我们会使用 new 关键字来实例化,但在部分字段和属性声明的时候,这些类型已经是在旁边给出,且不能使用 var 代替的。因此,我们必须这么写:

public Person XiaoMing { get; private set; } = new Person();

例如上面的类似写法。可以看到,实例化里必须要写上类型,但这个类型名称在上下文里已经给出,所以不必去写应该也知道,但 C# 一直没考虑省略的问题。直到 C# 9,这个特性才提上日程:

public Person XiaoMing { get; private set; } = new();

比如这样。

2. 基于平台的整型(Native integers)

C# 提供了两个新的关键字 nint 和 nuint 用来表达底层平台的 int 不确定(随机器位数大小变化的 int 类型)。

因为 C# 的 int 和 uint 都是固定的 4 字节数据,所以是不可变的。当我们不得不转换到底层(调用底层函数)的时候。

3. 模式匹配 3.0(Pattern matching improvements)

从 C# 7 开始,C# 开始支持模式匹配。模式匹配说白了就是基于数据的具体类型进行分情况讨论的一种用法。当一个类型需要从模糊转具体的时候,就需要模式匹配来判别具体的类型。C# 早期有 is 可以判断数据类型,有 as 可以进行尝试转换。但 C# 一直觉得这些写法还不够精简,所以发明了模式匹配的格式。

来看看 C# 每一个版本的模式匹配吧:

  • 模式匹配 1.0(C# 7):类型判别和转换一条蛇服务:o is T t、null 校验:o is null、switch 匹配类型;
  • 模式匹配 2.0(C# 8):类型属性和字段匹配模型(递归模式):sunnie is { Age: 24, Gender: 'm' }、switch 表达式风格的类型匹配。

C# 9 开始添加两种新的模式匹配模型:and/or/not 匹配模型和数据范围校验。

(1)and/or/not 模型

C# 新增了三个关键字:and、or 和 not。它们专门用来检测和校验数据的具体类型,以及数值是否是正常结果。与此同时,也可以校验递归模式。

因为用法比较多,所以这里就阐述其中一部分的用法:

bool b1 = o is Person and { Age: 24 } sunnie;
if (b1)
{bool b2 = sunnie is { Age: 24 } or { Gender: 'm' };if (b2){bool b3 = sunnie is { Age: not 24 };bool b4 = sunnie is not null;bool b5 = sunnie is not ({ Age: 80 } or { Gender: 'f'});}
}

(耸肩)看不懂对吧。

其实也不难。b1 是判断 o 是不是 Person 类型的。如果是,再看 Age 是不是 24。如果是,sunnie 才被正常转换过来。换句话说,b1 为 true 的时候,sunnie 变量必定有值(不是未赋值状态),且 Age 属性肯定是 24 这个结果。

b2 是看 sunnie 的 Age 是 24 或者 Gender 是 'm'。只要满足至少其中一个,就可以了。

b3 是看 sunnie 的 Age 不是 24 才满足要求,bool 才是 true,b4 是看 sunnie 不是 null 才满足要求,而 b5 是看 sunnie 的 Age 既不是 80,Gender 也不能是 'f',b5 才为 true。其中 b5 最复杂。不过你可以用数学上的取反来看:“非 A 且 非 B”的等价写法是“非 (A 或 B)”,所以 b5 也可以这么写:

bool b5 = sunnie is not { Age: 80 } and not { Gender: 'f' };

bool b5 = sunnie is { Age: not 80 } and { Gender: not 'f' };

但是需要你注意的是,模式匹配所比较的数值都必须是常量,即这里的 24、80、'f' 等。

稍微复杂一点的就是看 not 的推断了。我们可以认为,用大括号或者小括号一起判断的内容是且的关系,但对这样的条件取反就比较难理解了:

bool b = a is not { Prop1: < 20, Prop2: >= 40 };

这样的条件意味着什么呢?我们拆解语句,先把 not 改成可以看的办法:

bool b = !(a is { Prop1: < 20, Prop2: >= 40 });

然后取反。

bool b = a.Prop1 >= 20 || a.Prop2 < 40;

这里稍微注意一点的是,大括号连接表示 and,在取反时将改为 or,所以需要分开写。

(2)数值范围校验

除了这些判断方式外,C# 9 还允许在不知道数据类型的情况下对数据大小进行校验:

这个就表示,当 o 是 int 且范围是 10 到 20 之间、或是 float 且范围是 (float)10 到 (float)20 之间、或是 decimal 且范围是 (decimal)10 到 (decimal)20 之间,这三种情况之一,b 就为 true。

bool b = o is> 10 and < 20 or> 10F and < 20F or> 10M and < 20M;

(3)or 模式不能把类型校验变量内联到 is 校验里

我们刚才的 and 内联就可以将条件成立后的结果直接写到校验表达式 is 的最后。实际上 not 也可以:

if (o is not int i) { // ... } else { // Code can using 'i'. }

因为它完全等价于

if (!(o is int i)) { // ... } else { // Code can using 'i'. }

4. 静态 Lambda(Static lambdas)

当 Lambda 表达式不捕获数据的时候,我们允许使用 static 修饰 Lambda,来确保以后修改 API 和代码的时候,该 Lambda 不能捕获任何外部的数据。

Func<Person, int> selector = static (person) => person.Age;

另外,它也可以应用于静态的匿名函数上。

Func<Person, int> selector = static delegate(Person person)
{return person.Age;
}

这一点就是从 C# 8 出现的静态本地函数那边学来的。

5. 记录类型(Records)

(1)基本用法

记录类型是一种特殊的引用类型,它用来简化我们书写代码的代码量,同时达到了灵活程度。假如我们要定义一个轻量级的类型,这个类型就用来记录数据的结果信息的时候,我们就可以使用记录。如果我们这个类型假设带有 A、B、C 和 D 四个属性,我们直接写 A、B、C 和 D 要写四遍属性,然后初始化的时候要给这个类型传四个参数的构造器,对应赋值到这四个属性上;而且如果我们要重写 ToString 和一些比较函数的操作的时候,还得自己写。

这太麻烦了吧!所以 C# 9 里就简化了这些步骤,我们直接用一个 record 关键字就可以了:

public sealed record R(int A, int B, int C, int D);

你看,这么写是不是很简单?在编译后,R 会展开,把小括号里的四个参数直接输出变为属性,R 也会直接改成 public sealed class R,里面包含了很多已经帮你写好了的方法:ToString、Deconstruct 和 Clone 等等。

如果你不想要这些已经自动实现好的方法,你可以自己手写,不过请注意,Clone 方法不能重写,它在记录类型里有特殊用途,这一点我们在之后的特性 with 表达式里讲。

就像上面这样,把自动属性写在小括号里,这种我们叫做对位记录(Positional record)。

更广泛地,对位记录可以没有自动属性,所以小括号里没东西,所以干脆就不让你写小括号了,所以直接这么写:

public sealed record R;

写了后面的空小括号反而会报错,告诉你:一个对位记录必须带有至少一个自动属性在小括号里。

(2)记录的继承关系

就现在来说,记录类型只能继承自一个记录。但最终被翻译为 class 的缘故,也有很多声音想要让团队去支持 record 继承自普通类的,或者普通类继承自 record 的,不过这一点我们只能耐心等待了。

(3)它能做哪些用?

如果你把它当成一个普通的类的话,我想你应该就懂了:只要类能做的,它基本上都能做,而且还能简化书写的代码量,唯独就是这里的 Clone 你不能重写。记录类型甚至都把 operator == 和 operator != 都给你重写好了。

因为是简化类类型的书写才有的记录类型,所以它支持 sealed、abstract 等绝大多数修饰符,但 static 不行。因为 static class 用来提供静态操作的,但记录类型不是,它专门记录一个执行操作的效果,封装一个结果类型才出现的,所以没有 static record。

6. with 表达式(with expression)

(1)基本用法

为了解释记录的原理,C# 专门提供了两个语法用来表示它们,一个叫 with 表达式,一个是 init 赋值器。我们先来说一下这个语法。

with 用来拓展一个记录,使得新的记录可以独立于原类型,并且在此之上更改和增加数据成员的数值:

Person p = new() { Name = "XiaoMing", Age = 24 };
Person q = p with { Name = "XiaoWang", IsGeBi = true };

我相信你可以看到这段代码不用解释就能理解意思。

对!就是直接给 q 赋值 p 的所有数据后,在此之上更新 Name 和 IsGeBi 的数值信息。

(2)with 的原理

我很高兴看到了这个语法机制,但我不得不说一下它的具体执行行为。with 关键字在后面跟上一个对象初始化器,以完成对新的数据的更新。当然,没有更新的数据就保持原本的数值,比如这里的 q 的 Age 还是 24。那么它是怎么实现的呢?

还记得前面点到了一个内容吗?记录会默认实现一个不可修改的方法 Clone。这个方法为啥不可修改呢?因为这个方法是用来给 with 用的。换句话说,记录类型也是一个鸭子类型,只要你实现了 Clone 方法就能用 with,因为内部会调用 Clone 方法后,才会修改掉那些具体的数值。如果 C# 让你修改 Clone,整个执行操作的意义都变化了,以至于 with 的意义也发生了变化。C# 为了避免你这么做,就定了这么一个语法盐,不让你去动 Clone。

7. init 赋值器(init setter)

(1)基本用法

第二个用来理解记录类型不可变的语法就是这个 init 关键字了。init 和 get 还有 set 这两个关键字被定义成完全平级的关键字,你可以当成一个特殊的 set 块,只是这个块在初始化就用得上一次以外就不可再修改了。比如说

public class Person
{public char Gender { get; init; }public int Age { get; init; }
}

这个和 set 的使用语法相当类似,初始化器也能用:

var p = new Person { Gender = 'm', Age = 24 };

但你再次修改就不行了:

p.Age = 25; // Wrong.

这一点和普通类的 get 加 set 有区别。

另外,既然 set 和 init 都是用作赋值行为(作为赋值器),所以不能同时出现,一次就只能有一个。

(2)那么它和 get-only 的属性有啥区别呢?

前文说了 get; init; 组合和 get; set; 组合的区别,下面来说一下 get; init; 和 get; 的区别。

get; init; 可以让属性赋初始值,而 get; 也可以。举个例子:

public class A
{public A(int age) => Age = age; // Correct.public int Age { get; }
}
​
public class B
{public B(int age) => Age = age; // Correct.public int Age { get; init; }
}

这么来看,它们是没有区别的。唯一的区别就是初始化赋值的时候。前者只有 get 所以不让用对象初始化器;但后者就不一样了,后者保证了对象可以在初始化的时候赋值一次,而这个“初始化”可以通过构造器本身,也可以对象初始化器。这一点来说,只有 get 的属性是不能用对象初始化器的。

(3)init 是咋实现的?

嗯哼,这个底层原理可能会让你很失望,因为……确实很简单。

一个 get; set; 组合的属性,在底层被翻译成了一个字段、一个取值方法和一个赋值方法。它的代码是这样的:

private int _age;
​
public int Age
{get => _age;set => _age = value;
}

而 init 的呢?

private readonly int _age;
​
public int Age
{get => _age;set => _age = value;
}

发现区别了吗?对了,底层字段多了一个 readonly 而已。

这就很奇怪了朋友们。这样的话 set 依然是随意赋值的,那它是怎么保证你在其他地方随意赋值的时候报警告信息的?

答案其实很简单。带 readonly 的内部字段,那么 set 就可以改成 init,因为 readonly 字段保证了字段只在初始化的时候提供修改和赋值,之后就一直保持不变。这不是就是完全类似于 init 的行为吗?

是的,要说区别呢,就只有一个,readonly 是只能在构造器里赋值;而 init 可以在初始化器里,也可以在构造器里。不知道你想过一个问题没有,有没有 public readonly 组合?有对吧,那么你可不可能从外界来为这个对象赋值呢?显然就不可能了。因为它们都是在类内部初始化的,要么在它旁边直接赋值,要么就是构造器了。

那么,还是没解释怎么防止赋值的啊。实际上,这一点是从编译器层面搞定的。它检测你的赋值行为,是在初始化器里还是其他别的什么地方。只要是别的地方,肯定就是不允许的,自然就会报错了。

(4)啥时候用 init 呢?

你去思考一下,init 是为了配合记录类型才出现的一种语法,这也就意味着它在记录类型里就可以体现出非常神奇的语义。

记录是用来存储一些结果数据信息的,那么这些数据肯定是单纯用于显示和输出的,所以在初始化后它们肯定不能修改,即不可变。所以,在我们平时的编码过程中,只要对象专门用来表示初始化后不再变更的行为,就可以用 init 来替代 set。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/15921.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最近5星好评的华为的书《常变与长青》

常变与长青 (豆瓣) 作者简介 郭平&#xff0c;1988年加入华为&#xff0c;历任产品开发部项目经理、供应链总经理、总裁办主任、管理工程部总裁、企业发展部总裁、终端公司董事长兼总裁、公司轮值CEO、财经委员会主任、公司副董事长、轮值董事长等职务&#xff0c;现任公…

微信小程序毕业设计-学生知识成果展示与交流系统项目开发实战(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

接口自动化核心模块Requests详解(二)

一、概述 使用requests进行接口测试时&#xff0c;主要使用get 和post两种方式,两种请求方式的传参模式和方法是完全不一样的 二、传参实战 2.1 post传参的数据格式 使用post进行传参时&#xff0c;有三种数据格式&#xff0c;data(键值对的字典)&#xff0c;json(有嵌套的…

Keyshot v11 解锁版安装教程 (3D光线追踪与全域光渲染程序)

前言 keyshot是一款实时渲染模式的软件。实时渲染是目前比较流行的一种渲染方式&#xff0c;优点是快速。调节的材质&#xff0c;灯光修改&#xff0c;光影变化等修改的各种参数结果&#xff0c;所见即所得&#xff0c;意思是你在软件操作界面看到的&#xff0c;就是最终的结果…

props配置项

src/App.vue: <template><div><Student name"JOJO" sex"男酮" :age"20" /></div> </template><script>import Student from ./components/Student.vueexport default {name:App,components: { Student },}…

绘制t-SNE图

什么是t-SNE图&#xff1f; 如下图&#xff0c;下图来源于论文Contrastive Clustering 一般用于分类问题/对比学习。 作用&#xff1f; 体现出经过层层训练&#xff0c;类内越来越紧密&#xff0c;类间差异越来越大&#xff1b;或者也可以做消融可视化。 怎么画&#xff1f…

vim操作手册

vim分为插入模式、命令模式、底行模式。 插入模式&#xff1a;编辑模式 命令模式&#xff1a;允许使用者通过命令&#xff0c;来进行文本的编辑控制 底行模式&#xff1a;用来进行让vim进行包括但不限于shell进行交互 w&#xff1a;保存 wq&am…

Actor-critic学习笔记-李宏毅

Policy Gradient review ∇ R ‾ θ 1 N ∑ n 1 N ∑ t 1 T n ( ∑ t ′ t T n γ t ′ − t r t ′ n − b ) ∇ log ⁡ p θ ( a t n ∣ s t n ) \nabla \overline{R}_\theta \frac{1}{N}\sum_{n 1}^{N}\sum_{t 1}^{T_n}(\sum_{tt}^{T_n}\gamma^{t-t}r_{t}^n-b)\nabl…

提高软件团队开发速度和质量的策略

在现代软件开发中&#xff0c;提高开发速度和质量是每个团队追求的目标。高效的开发流程不仅能缩短产品的上市时间&#xff0c;还能确保软件的稳定性和可靠性。本文将探讨提高软件团队开发速度和质量的各种策略&#xff0c;包括技术、流程、团队文化等方面。 一、采用敏捷开发…

SwiftUI中的Stepper(系统Stepper以及自定义Stepper)

本篇文章主要介绍一下Stepper&#xff0c;这个组件在UIKit中也已经有较长的历史了&#xff0c;下面看看在SwiftUI中如何使用&#xff0c;有哪些更加便捷的方法呢&#xff1f; Stepper减号(-)和加号()按钮&#xff0c;可以点击后以指定的数值进行加减。 基础初始化方法 Stepp…

【LinuxC语言】使用按位或操作传递多标志参数

文章目录 前言一、使用按位或操作传递多标志参数的原理二进制表示按位或操作检查标志图示二、C语言示例代码总结前言 在C语言编程中,经常需要在函数调用中传递多个选项或配置标志。直接传递多个参数可能会导致代码繁琐且难以维护。为了解决这个问题,C语言提供了一种通过按位…

【动态规划】斐波那契数列模型(C++)

目录 1137.第N个泰波那契数 解法&#xff08;动态规划&#xff09; 算法流程 1. 状态表⽰&#xff1a; 2. 状态转移⽅程&#xff1a; 3. 初始化&#xff1a; 4. 填表顺序&#xff1a; 5. 返回值&#xff1a; C算法代码 优化&#xff1a; 滚动数组 测试&#xff1a; …

HP Laptop 15s-fq2xxx,15s-fq2706TU原厂Win11系统镜像下载

惠普星15青春版原装Windows11系统&#xff0c;恢复出厂开箱状态oem预装系统&#xff0c;带恢复重置还原 链接&#xff1a;https://pan.baidu.com/s/1t4Pc-Q0obApLkG8o_9Kkkw?pwdduzj 提取码&#xff1a;duzj 适用型号&#xff1a;15s-fq2xxx&#xff0c;15s-fq2000 15s-f…

ROS2入门21讲__第19讲__Rviz:三维可视化显示平台

目录 前言 Rviz三维可视化平台 Rviz介绍 运行方法 彩色相机仿真与可视化 仿真插件配置 运行仿真环境 图像数据可视化 三维相机仿真与可视化 仿真插件配置 运行仿真环境 点云数据可视化 激光雷达仿真与可视化 仿真插件配置 运行仿真环境 点云数据可视化 Rviz v…

月薪5万是怎样谈的?

知识星球&#xff08;星球名&#xff1a;芯片制造与封测技术社区&#xff0c;星球号&#xff1a;63559049&#xff09;里的学员问&#xff1a;目前是晶圆厂的PE&#xff0c;但是想跳槽谈了几次薪水&#xff0c;都没法有大幅度的增长&#xff0c;该怎么办&#xff1f;“学得文武…

联想单机游戏联运SDK接入攻略

1. 接入流程 本文档主要介绍了联想单机游戏SDK接入流程、联想游戏提供的功能等。 1.1. 接入方式 1. 联想单机游戏SDK1.0版本支持“账号防沉迷”接入方式&#xff1b; a. 联想提供账号注册、登录等能力 b. 联想判断账号是否购买游戏&#xff0c;提供游戏支付购买能力 c. 联…

RobotFramework测试框架(13)--内置测试库

Builtln Evaluate方法 Evaluate。它可以做很多事情&#xff0c;主要的作用是可以直接调用Python的方法 一般用Evaluate都是前面放变量接收值&#xff0c;第三列是具体的运算表达式&#xff0c;第四列是要用到的Python的module。这里就是用random来进行一个随机数的生成 Cons…

基础6 探索JAVA图形编程桌面:集合组件详解

我们的团队历经了数不胜数的日夜&#xff0c;全力以赴地进行研发与精心调试&#xff0c;最终成功地推出了一款具有革命性意义的“图形化编程桌面”产品。这款产品的诞生&#xff0c;不仅极为彻底地打破了传统代码开发那长久以来的固有模式&#xff0c;更是把焦点聚集于解决长期…

Redis教程(十五):Redis的哨兵模式搭建

一、搭建Redis一主二从 分别复制三份Redis工作文件夹&#xff0c;里面内容一致 接着修改7002的配置文件&#xff0c;【redis.windows-service.conf】 port 7002 改成 port 7002 slaveof 127.0.0.1 7001 7003也同样修改 port 7003 slaveof 127.0.0.1 7001 这样就指定了700…

Android.mk简单介绍、规则与基本格式

文章目录 Android.mk与makefile区别Android.mk规则Android.mk基本格式 Android.mk与makefile区别 Android.mk 和 Makefile 都是用于构建代码项目的构建脚本文件&#xff0c;但是它们在特定上下文中有一些区别&#xff1a; Android.mk: Android.mk 是用于构建 Android 应用或库…