服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C#9特性record 类型、模式匹配、init 属性详情

C#9特性record 类型、模式匹配、init 属性详情

2022-12-01 12:08东邪独狐 C#

这篇文章主要介绍了C#的record 类型、模式匹配(Pattern Matching)、属性的 init 访问器三大特性,感兴趣的小伙伴请参考下面文章内容

C#的特性record 类型、模式匹配、init 属性

一、record 类型

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

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

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

?
1
2
3
4
5
6
public class Cat
{
    public string Nick { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 两个实例描述的是同一只猫
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 类型。

?
1
2
3
4
5
6
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,你可能要这样判断:

?
1
2
3
4
if(A.UserName == B.UserName && A.Password == B.Password)
{
    ..................
}

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

A == B

二、模式匹配(Pattern Matching)

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


          

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int 考试成绩 = 85;
 
          switch (考试成绩)
          {
              case 10:
                  Console.WriteLine("才考这么点破分啊");
                  break;
              case 50:
                  Console.WriteLine("还差一点,就合格了");
                  break;
              case 85:
                  Console.WriteLine("真是秀");
                  break;
              case 90:
                  Console.WriteLine("奇迹发生");
                  break;
          }

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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类表示一条订单信息。

?
1
2
3
4
5
6
7
8
9
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,做成了收益应该不错。

?
1
2
3
4
5
6
7
8
9
Order od = new Order
{
    ID = 11,
    Company = "大嘴狗贸易有限公司",
    ContactName = "陈大爷",
    Qty = 425.12f,
    UP = 1000.55M,
    Date = new(2020, 10, 27)
};

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

?
1
2
3
4
5
6
7
8
9
10
11
12
switch (od)
{
    case { Qty: > 1000f }:
        Console.WriteLine("发财了,发财了");
        break;
    case { Qty: > 500f }:
        Console.WriteLine("好家伙,年度大订单");
        break;
    case { Qty: > 100f }:
        Console.WriteLine("订单量不错");
        break;
}

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

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

?
1
2
3
4
5
6
7
from x in ...
    where x.A ...
    select new {
        Prop1 = ...,
        Prop2 = ...,
        ................
    }           

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

?
1
2
3
Cat {
    ..........
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
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 就省略了,变成

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

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

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

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

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

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

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

?
1
2
3
4
if (od is { Date: < DateTime.Now })
{
    ................
}

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

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

?
1
2
3
4
5
object n = 5000000L;
if(n is long)
{
    Console.WriteLine("它是个长整型");
}

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

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

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

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

?
1
2
3
4
5
6
7
8
9
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; 即可。

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

?
1
2
3
4
5
6
public class Dog
{
    public int No { get; } = 0;
    public string Name { get; } = "no name";
    public int Age { get; } = 1;
}

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

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

?
1
2
3
4
5
6
Dog x = new Dog
{
    No = 100,
    Name = "吉吉",
    Age = 4
};

试一下,编译会出错吧。

C#9特性record 类型、模式匹配、init 属性详情

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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;
    }
}

然后,这样初始化。

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

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

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

?
1
2
3
4
5
6
public class Dog
{
    public int No { get; init; }
    public string Name { get; init; }
    public int Age { get; init; }
}

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

?
1
2
3
4
5
6
Dog x = new Dog
{
    No = 100,
    Name = "吉吉",
    Age = 4
};

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

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

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

以上就是C#的record 类型、模式匹配、init 属性详情的详细内容,更多关于C#的record 类型、模式匹配、init 属性的资料请关注服务器之家其它相关文章!

原文链接:https://www.cnblogs.com/tcjiaan/p/13947928.html

延伸 · 阅读

精彩推荐
  • C#Visual Studio 中自定义代码片段的方法

    Visual Studio 中自定义代码片段的方法

    这篇文章主要介绍了Visual Studio 中自定义代码片段的方法,本文分步骤通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,...

    vkvi4272022-08-29
  • C#c# Async streams的使用解析

    c# Async streams的使用解析

    这篇文章主要介绍了c# Async streams的使用解析,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下...

    小码甲7732022-11-15
  • C#C#语言中的修饰符汇总

    C#语言中的修饰符汇总

    本文主要介绍的是C#语言中的修饰符,主要从四方面介绍,希望对大家有帮助,一起来看。...

    C#教程网9582021-11-01
  • C#C#去除DataTable重复数据的三种方法

    C#去除DataTable重复数据的三种方法

    这篇文章主要介绍了C#去除DataTable重复数据的三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友...

    无忧岛主5752022-11-02
  • C#C# ManualResetEvent用法详解

    C# ManualResetEvent用法详解

    这篇文章主要介绍了C# ManualResetEvent用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着...

    陌客&5042022-08-11
  • C#WPF实现上下滚动字幕效果

    WPF实现上下滚动字幕效果

    这篇文章主要为大家详细介绍了WPF实现上下滚动字幕效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    秋荷雨翔6812022-01-24
  • C#C#获取某路径文件夹中全部图片或其它指定格式的文件名的实例方法

    C#获取某路径文件夹中全部图片或其它指定格式的文件名的实例

    在本篇文章里小编给大家整理的是关于C#获取某路径文件夹中全部图片或其它指定格式的文件名的实例方法,需要的朋友们参考下。...

    小xin_不惊7562022-08-08
  • C#Unity实现倒计时功能

    Unity实现倒计时功能

    这篇文章主要为大家详细介绍了Unity实现倒计时功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    SakuraJI11922022-09-08