你一定会好奇:“老周,你去哪开飞机了?这么久没写博客了。”

老周:“我买不起飞机,开了个铁矿,挖了一年半的石头。谁知铁矿垮了,压死了几条蜈蚣,什么也没挖着。”

所以,这么丢死人的事,还是不要提了,爷爷从小教导我做人要低调……

一转眼,.NET 5 要来了,同时也带来了 C# 9。遥想当年,老周刚接触 .NET 1.1 的时候,才刚上大学;如今已经过去13年了。岁月是把水果刀,从来不饶人啊。

老周很少去写诸如“XXX新特性”之类的文章,总觉得没啥用处。不过,针对 C# 9,老周想说一点什么。

好,在开始之前,老周再次强调一下:这些语言新特性的东西,你千万不要特意去学习,千万不要,不要,不要,重要的事情讲四遍!这些玩意儿你只要看看官方给的说明,刷一遍就能掌握了,不用去学的。如果你连这些东东也要学习成本的话,我只想说句好唱不好听的话——你的学习能力真的值得怀疑

好了,下面开始表演。

第一出:record 类型

record ,我还是用原词吧,我知道有翻译为“记录类型”的说法。只是,只是,老周老觉得这不太好听,可是老周也找不出更好的词语,还是用回 record吧。

record 是引用类型,跟 class 很像(确实差不多)。那么,用人民群众都熟悉的 class 不香吗,为何要新增个 record 呢?答:为了数据比较的便捷。

不明白?没事,往下看。最近有一位热心邻居送了老周一只宠物:

    public class Cat
{
public string Nick { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}

这只新宠物可不简单,一顶一的高级吃货。鱼肉、猪肉、鸡腿、饼干、豆腐、面包、水果、面条、小麦、飞蛾……反正,只要它能塞进嘴里的,它都吃。

接下来,我们 new 两个宠物实例。

            // 两个实例描述的是同一只猫
Cat pet1 = new Cat
{
Nick = "松子",
Name = "Jack",
Age = 1
};
Cat pet2 = new Cat
{
Nick = "松子",
Name = "Jack",
Age = 1
}; // 居然不是同一只猫
Console.WriteLine("同一只?{0}", pet1 == pet2);

其实,两个实例描述的都是我家的乖乖。可是,输出的是:

同一只?False

这是因为,在相等比较时,人家关心的类型引用——引用的是否为同一个实例。但是,在数据处理方案中,我们更关注对象中的字段/属性是否相等,即内容比较。

现在,把 Cat 的声明改为 record 类型。

    public record Cat
{
public string Nick { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}

然后同样用上面的 pet1 和 pet2 实例进行相等比较,得到预期的结果:

同一只?True

record 类型让你省去了重写相等比较(重写 Equals、GetHashCode 等方法或重载运算符)的逻辑。

实际上,代码在编译后 record 类型也是一个类,但自动实现了成员相等比较的逻辑。以前你要手动去折腾的事现在全交给编译器去干。

假如,有一个 User 类型,用于表示用户信息(包括用户名、密码),然后这个 User 类型在数据处理方案中可能会产生N多个实例。例如你根据条件从EF模型中筛选出一个 User 实例 A,根据用户输入的登录名和密码产生了 User 实例 B。为了验证用户输入的登录信息是否正确,如果 User 是 class,你可能要这样判断:

if(A.UserName == B.UserName && A.Password == B.Password)
{
..................
}

但要是你把 User 定义为 record 类型,那么,一句话的工夫:

A == B

第二出:模式匹配(Pattern Matching)

"模式匹配"这个翻译感觉怪怪滴,老周还没想出什么更好的词语。模式匹配并不是什么神奇的东西,它只是在对变量值进行检测时的扩展行为。以前,老感觉C++/C# 的 switch 语句不够强大,因为传统的用法里面,每个 case 子句只能比较单个常量值。比如

            int 考试成绩 = 85;

            switch (考试成绩)
{
case 10:
Console.WriteLine("才考这么点破分啊");
break;
case 50:
Console.WriteLine("还差一点,就合格了");
break;
case 85:
Console.WriteLine("真是秀");
break;
case 90:
Console.WriteLine("奇迹发生");
break;
}

我幻想着,要是能像下面这样写就好了:

            switch (考试成绩)
{
case 0:
Console.WriteLine("缺考?");
break;
case > 0 && <= 30:
Console.WriteLine("太烂了");
break;
case > 30 && < 60:
Console.WriteLine("还是不行");
break;
case >= 60 && < 80:
Console.WriteLine("还得努力");
break;
case >= 80 && < 90:
Console.WriteLine("秀儿,真优秀");
break;
case >= 90 && <= 100:
Console.WriteLine("不错,奇迹");
break;
}

等了很多年很多年(“千年等一回,等……”)以后,终于可以实现了。

            switch (考试成绩)
{
case 0:
Console.WriteLine("缺考?");
break;
case > 0 and <= 30:
Console.WriteLine("太烂了");
break;
case > 30 and < 60:
Console.WriteLine("还是不行");
break;
case >= 60 and < 80:
Console.WriteLine("还得努力");
break;
case >= 80 and < 90:
Console.WriteLine("秀儿,真优秀");
break;
case >= 90 and <= 100:
Console.WriteLine("不错,奇迹");
break;
}

哟西,真香。

有时候,不仅要检测对象的值,还得深入到其成员。比如下面这个例子,Order类表示一条订单信息。

    public class Order
{
public int ID { get; set; }
public string Company { get; set; }
public string ContactName { get; set; }
public float Qty { get; set; }
public decimal UP { get; set; }
public DateTime Date { get; set; }
}

前不久,公司接到一笔Order,做成了收益应该不错。

            Order od = new Order
{
ID = 11,
Company = "大嘴狗贸易有限公司",
ContactName = "陈大爷",
Qty = 425.12f,
UP = 1000.55M,
Date = new(2020, 10, 27)
};

假如我要在变量 od 上做 switch,看看,就这样:

            switch (od)
{
case { Qty: > 1000f }:
Console.WriteLine("发财了,发财了");
break;
case { Qty: > 500f }:
Console.WriteLine("好家伙,年度大订单");
break;
case { Qty: > 100f }:
Console.WriteLine("订单量不错");
break;
}

咦?这,这是什么鬼?莫惊莫惊,这不是鬼。它的意思是判断 Qty 属性的值,如果订单货量大于 100 就输出“订单量不错”;要是订单货量大于 1000,那就输出“发财了,发财了”。

但你会说,这对大括号怎么来的呢?还记得这种 LINQ 的写法吗?

    from x in ...
where x.A ...
select new {
Prop1 = ...,
Prop2 = ...,
................
}

new { ... } 是匿名类型实例,那如果是非匿名类型呢,看看前面的 Cat 实例初始化。

     Cat {
..........
}

这就对了,这对大括号就是构造某实例的成员值用的,所以,上面的 switch 语句其实是这样写的:

            switch (od)
{
case Order{ Qty: > 1000f }:
Console.WriteLine("发财了,发财了");
break;
case Order{ Qty: > 500f }:
Console.WriteLine("好家伙,年度大订单");
break;
case Order{ Qty: > 100f }:
Console.WriteLine("订单量不错");
break;
}

Order{ ... } 就是匹配一个 Order 对象实例,并且它的 Qty 属性要符合 ... 条件。由于变量 od 始终就是 Order 类型,所以,case 子句中的 Order 就省略了,变成

                case { Qty: > 1000f }:
Console.WriteLine("发财了,发财了");
break;

如果出现多个属性,则表示为多个属性设定匹配条件,它们之间是“且”的关系。比如

                case { Qty: > 100f, Company: not null }:
Console.WriteLine("订单量不错");
break;

猜猜啥意思?这个是可以“望文生 yi”的,Qty 属性的值要大于 100,并且 Company 属性的值不能为 null。不为 null 的写法是 not null,不要写成 !null,因为这样太难看了。

如果你的代码分支较少,你可以用 if 语句的,只是得配合 is 运算符。

            if (od is { UP: < 3000M })
{
Console.WriteLine("报价不理想");
}

但是,这个写法目前有局限性,它只能用常量值来做判断,你要是这样写就会报错。

            if (od is { Date: < DateTime.Now })
{
................
}

DateTime.Now 不是常量值,上面代码无法通过编译。

is 运算符以前是用来匹配类型的,上述的用法是它的语法扩展。

            object n = 5000000L;
if(n is long)
{
Console.WriteLine("它是个长整型");
}

进化之后的 is 运算符也可以这样用:

            object n = 5000000L;
if(n is long x)
{
Console.WriteLine("它是个长整型,存放的值是:{0}", x);
}

如果你在 if 语句内要使用 n 的值,就可以顺便转为 long 类型并赋值给变量 x,这样就一步到位,不必再去写一句 long x = (long)n 。

如果 switch... 语句在判断之后需要返回一个值,还可以把它变成表达式来用。咱们把前面的 Order 例子改一下。

            string message = od switch
{
{ Qty: > 1000f } => "发财了",
{ Qty: > 500f } => "年度大订单",
{ Qty: > 100f } => "订单量不错",
_ => "未知"
}; Console.WriteLine(message);

这时候你得注意:

1)switch 现在是表达式,不是语句块,所以最后大括号右边的分号不能少;

2)因为 switch 成了表达式,就不能用 case 子句了,所以直接用具体的内容来匹配;

3)最后返回“未知”的那个下划线(_),也就是所谓的“弃婴”,哦不,是“弃元”,就是虽然赋了值但不需要使用的变量,可以直接丢掉。这里就相当于 switch 语句块中的 default 子句,当前面所有条件都不能匹配时,就返回“未知”。

第三出:属性的 init 访问器

要首先得知道,这个 init 只用于只读属性的初始化阶段,对于可读可写的属性,和以前一样,直接 get; set; 即可。

有人说这个 int 不知干啥用,那好,咱们先不说它,先来看看 C# 前些版本中新增的属性初始化语句。

    public class Dog
{
public int No { get; } = 0;
public string Name { get; } = "no name";
public int Age { get; } = 1;
}

你看,这样就可以给属性分配初始值了,那还要 init 干吗呢?

好,我给你制造一个问题——我要是这样初始化 Dog 类的属性,你试试看。

            Dog x = new Dog
{
No = 100,
Name = "吉吉",
Age = 4
};

试一下,编译会出错吧。

有些情况,你可以在属性定义阶段分配初始值,但有些时候,你必须要在代码中初始化。在过去,我们会通过定义带参数的构造函数来解决。

    public class Dog
{
public int No { get; } = 0;
public string Name { get; } = "no name";
public int Age { get; } = 1; public Dog(int no, string name, int age)
{
No = no;
Name = name;
Age =
age;
}

}

然后,这样初始化。

   Dog x = new(1001, "吉吉", 4);

可是,这样做的装逼指数依然不够高,你总不能每个类都来这一招吧,虽然不怎么辛苦,但每个类都得去写一个构造函数,不利落。

于是,init 访问器用得上了,咱们把 Dog 类改改。

    public class Dog
{
public int No { get; init; }
public string Name { get; init; }
public int Age { get; init; }
}

你不用再去写带参数的构造函数了,实例时直接为属性赋值。

            Dog x = new Dog
{
No = 100,
Name = "吉吉",
Age = 4
};

这样一来,这些只读属性都有默认的初始值了。

当然,这个赋值只在初始化过程中有效,初始化之后你再想改属性的值,没门!

            x.Name = "冬冬";  //错误
x.Age = 10; //错误

嗯,好了,以上就是老周对 C# 9 新特性用法的一些不成文的阐述。看完后你就别说难了。

说说 C# 9 新特性的实际运用的更多相关文章

  1. SQL Server 2014 新特性——内存数据库

    SQL Server 2014 新特性——内存数据库 目录 SQL Server 2014 新特性——内存数据库 简介: 设计目的和原因: 专业名词 In-Memory OLTP不同之处 内存优化表 ...

  2. ElasticSearch 5学习(10)——结构化查询(包括新特性)

    之前我们所有的查询都属于命令行查询,但是不利于复杂的查询,而且一般在项目开发中不使用命令行查询方式,只有在调试测试时使用简单命令行查询,但是,如果想要善用搜索,我们必须使用请求体查询(request ...

  3. [干货来袭]C#6.0新特性

    微软昨天发布了新的VS 2015 ..随之而来的还有很多很多东西... .NET新版本 ASP.NET新版本...等等..太多..实在没消化.. 分享一下也是昨天发布的新的C#6.0的部分新特性吧.. ...

  4. CSS3新特性应用之结构与布局

    一.自适应内部元素 利用width的新特性min-content实现 width新特性值介绍: fill-available,自动填充盒子模型中剩余的宽度,包含margin.padding.borde ...

  5. 【译】Meteor 新手教程:在排行榜上添加新特性

    原文:http://danneu.com/posts/6-meteor-tutorial-for-fellow-noobs-adding-features-to-the-leaderboard-dem ...

  6. 跨平台的 .NET 运行环境 Mono 3.2 新特性

    Mono 3.2 发布了,对 Mono 3.0 和 2.10 版本的支持不再继续,而且这两个分支也不再提供 bug 修复更新. Mono 3.2 主要新特性: LLVM 更新到 3.2 版本,带来更多 ...

  7. Atitit opencv版本新特性attilax总结

    Atitit opencv版本新特性attilax总结 1.1. :OpenCV 3.0 发布,史上功能最全,速度最快的版1 1.2. 应用领域2 1.3. OPENCV2.4.3改进 2.4.2就有 ...

  8. es6 新特性2

    es6其他几个非常有用的新特性. import export 这两个家伙对应的就是es6自己的module功能. 我们之前写的Javascript一直都没有模块化的体系,无法将一个庞大的js工程拆分成 ...

  9. ES6 新特性

    ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准.因为当前版本的ES6是在2015年发布的,所以又称ECMAScript 2015. 也就是说,ES6就是ES2015. ...

  10. 谈谈我的微软特约稿:《SQL Server 2014 新特性:IO资源调控》

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 撰写经历(Experience) 特约稿正文(Content-body) 第一部分:生活中资源 ...

随机推荐

  1. 01Linux系统简介

    Linux 简介 一.介绍 1.1 内容 Linux 的历史,Linux 与 Windows 的区别等知识. 1.2 知识点 linux为何物 linux历史简介 linux重要人物 linux与wi ...

  2. ASP。NET Core Blazor CRUD使用实体框架和Web API

    下载source code - 1.7 MB 介绍 *请查看我的Youtube视频链接来学习ASP.NET Core Blazor CRUD使用实体框架和Web API. 在本文中,我们将了解如何为A ...

  3. List移除另外一个list的时候报错,java.lang.UnsupportedOperationException

    问题 编写代码的时候,使用Mybatis-plus分页查询返回的list,移除自己new的ArrayList报错 根据异常信息,发现mybatis-plus分页查询返回的list底层并没有实现remo ...

  4. Docker笔记2:Docker 镜像仓库

    Docker 镜像的官方仓库位于国外服务器上,在国内下载时比较慢,但是可以使用国内镜像市场的加速器(比如阿里云加速器)以提高拉取速度. Docker 官方的镜像市场,可以和 Gitlab 或 GitH ...

  5. devops-jenkins-Pipeline基础语法

    1. jenkins-Pipeline基础语法  1) jenkins-Pipeline总体介绍 • Pipeline,简而言之,就是一套运行与jenkins上的工作流框架,将原本独立运行于单个或多个 ...

  6. 要是想让程序跳转到绝对地址是0x100000去执行

    要对绝对地址0x100000赋值,我们可以用 (unsigned int*)0x100000 = 1234; 那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做? *((void (* ...

  7. 【动态规划】DP搬运工3

    UPD:修了点锅(啊昨天居然写脑抽了) 题目内容 给定两个长度为 \(n\) 的序列,定义 \(magic(A,B)=\sum\limits_{i=1}^n \max(A_i,B_i)\). 现在给定 ...

  8. 理解Go协程与并发(转)

    理解Go协程与并发   协程 Go语言里创建一个协程很简单,使用go关键字就可以让一个普通方法协程化: Copy package main import ( "fmt" " ...

  9. go 结构体函数

    package main import "fmt" type Dog struct { Name string } func (d *Dog) speak() string { r ...

  10. linux(centos8):awk在系统运维中的常用例子

    一,awk的作用 1,用途 AWK是一种处理文本文件的语言, 是一个强大的文本分析工具 2,awk和sed的区别 awk适合按列(域)操作, sed适合按行操作 awk适合对文件的读取分析, sed适 ...