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

目录
  • C#的特性record 类型、模式匹配、init 属性
    • 一、record 类型
    • 二、模式匹配(Pattern Matching)
    • 三、属性的 init 访问器

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

一、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;

猜猜啥意思?这个是可以“望文生义”的,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; 即可。

有人说这个 init 不知干啥用,那好,咱们先不说它,先来看看 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#的record 类型、模式匹配、init 属性详情的详细内容,更多关于C#的record 类型、模式匹配、init 属性的资料请关注我们其它相关文章!

(0)

相关推荐

  • C语言朴素模式匹配算法实例代码

    一.什么是字符串的模式匹配? 字符串模式匹配:在主串中找到与模式串相同的子串,并返回其所在位置. 注意: ①.子串--主串的一部分,一定存在. ②.模式串--不一定能在主串中找到 二.朴素模式匹配算法 主串长度为n,模式串长度为m. 朴素模式匹配算法:将主串中所有长度为m的子串依次与模式串匹配对比,直到找到一个完全匹配的子串,或所有的子串都不匹配为止. 最多对比n-m+1个子串 (一)通过数组下标实现朴素模式匹配算法 若当前⼦串匹配失败,则主串指针 i 指向下⼀个⼦串的第⼀个位置,模式串指针 j

  • C#9新特性之增强的模式匹配

    Intro C# 9 中进一步增强了模式匹配的用法,使得模式匹配更为强大,我们一起来了解一下吧 Sample C# 9 中增强了模式匹配的用法,增加了 and / or / not 操作符,而且可以直接判断属性,来看一下下面的这个示例: var person = new Person(); // or // string.IsNullOrEmpty(person.Description) if (person.Description is null or { Length: 0 }) { Con

  • c# 使用模式匹配以及 is 和 as 运算符安全地进行强制转换

    由于是多态对象,基类类型的变量可以保存派生类型. 要访问派生类型的实例成员,必须将值强制转换回派生类型. 但是,强制转换会引发 InvalidCastException 风险. C# 提供模式匹配语句,该语句只有在成功时才会有条件地执行强制转换. C# 还提供 is 和 as 运算符来测试值是否属于特定类型. 下面的示例演示如何使用模式匹配 is 语句: class Animal { public void Eat() { Console.WriteLine("Eating."); }

  • C# 9.0 新特性之模式匹配简化的实现

    记得在 MS Build 2020 大会上,C# 语言开发项目经理 Mads Torgersen 宣称 C# 9.0 将会随着 .NET 5 在今年 11 月份正式发布.目前 .NET 5 已经到了 Preview 5 阶段了,C# 9.0 也已经初具规模.忍不住激动的心情,暂停更新<C#.NET 拾遗补漏>系列几天,先要和大家分享一下我了解到的 C# 9.0 的新特性.由于新特性比较多,所以会分成几篇来讲.这是第一篇,专讲模式匹配这个特性的简化. 模式匹配(Pattern Matching)

  • C#8 的模式匹配实现

    C# 7 里面的Pattern Mathing 更多内容请查看官方文档:https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-8#more-patterns-in-more-places?WT.mc_id=DT-MVP-5003302 is 模式 switch 和 when C# 8 里面的Pattern Matching 使用Deconstructor 和 位置匹配模式 下面两个类Teacher和Student都由构造函

  • C语言数据结构之模式匹配字符串定位问题

    C语言数据结构之模式匹配字符串定位问题 主要实现了三种字符串的模式匹配,主要包括字符串子操作的集合,字符串指针回溯,和KMP算法 头文件  #ifndef INDEXHEAD_H_INCLUDED #define INDEXHEAD_H_INCLUDED #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXLEN 255 typedef char Sstring[MAXLEN + 1

  • C语言数据结构中串的模式匹配

    C语言数据结构中串的模式匹配 串的模式匹配问题:朴素算法与KMP算法 #include<stdio.h> #include<string.h> int Index(char *S,char *T,int pos){ //返回字串T在主串S中第pos个字符之后的位置.若不存在,则函数值为0. //其中,T非空,1<=pos<=StrLength(s). int i=pos; int j=1; while(i<=S[0]&&j<=T[0]){ i

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

    目录 C#的特性record 类型.模式匹配.init 属性 一.record 类型 二.模式匹配(Pattern Matching) 三.属性的 init 访问器 C#的特性record 类型.模式匹配.init 属性 一.record 类型 record ,我还是用原词吧,我知道有翻译为"记录类型"的说法.只是,只是,老周老觉得这不太好听,可是老周也找不出更好的词语,还是用回 record吧. record 是引用类型,跟 class 很像(确实差不多).那么,用人民群众都熟悉的

  • .NET 6中为record类型自定义Equals方法

    目录 一.重写Equals方法 二.自定义Equals方法 三.结论 前言: record类型,这是一种新引用类型,而不是类或结构.record与类不同,区别在于record类型使用基于值的相等性. 例如: public record DemoRecord(int id); public class DemoClass {     public DemoClass(int id)     {         this.id = id;     }     public int id { get;

  • C# 9 新特性——record的相关总结

    Intro C# 9 中引入了 record,record 是一个特殊类,用它来实现 model 在有些情况下会非常的好用 Sample record RecordPerson { public string Name { get; init; } public int Age { get; init; } } record RecordPerson2(string Name, int Age); public static void MainTest() { var p1 = new Reco

  • C#特性 匿名类型与隐式类型局部变量使用介绍

    在本篇中我要介绍两个概念,我觉得这两个东西必须一起来介绍,这样才能连贯. C# 2.0里我们已经匿名方法了,现在类型也玩起匿名来了,怪不得大家"举报"的时候都喜欢匿名,为啥?因为匿名被举报人就找不着报复对象了呗,是的,匿名就是把名字隐藏起来,没有名字谁还能找得到你啊. 匿名类型 在C#里有这样一些类型,它是作为临时储存数据的,生命周期只在这个方法内,方法结束了,这个类型的生命周期也没有了.那么这里我们就可以使用一个匿名类型. 复制代码 代码如下: var KeyPair = new {

  • 使用@Value 注入 List 类型的配置属性需要注意的 BUG

    @Value 注入 List 类型的配置属性 @Value 注解可以方便的帮助我们注入配置属性值. 那么当注入一个 List 类型时该怎么做呢? 想必大家都会知道,可以使用下面这种写法: @Value("#{'${zf.ids}'.split(',')}") private List<Integer> ids; 上面的配置简单说下就是,先使用 ${zf.ids} 拿到配置文件中 zf.ids 属性的值,然后使用 #{} 也就是 SPEL 表达式语言进行按,拆分,得到的结果转

  • Vue中的 watch监听属性详情

    目录 一.watch监听一般数据的变化(数值,字符串,布尔值) 1.数值 2.字符串 3.布尔值 二.watch 监听 复杂类型数据的变化 1.对象 2.数组 3.对象数组 4.对象数组的属性 首先要确认 watch是一个对象,要当成对象来使用. 键:就是那个,你要监听的那个家伙: 值:可以是函数,当你监控的家伙发生变化时,需要执行的函数,这个函数有两个形参 第一个是当前值(新的值),第二个是更新前的值(旧值) 值也可以是函数名:不过这个函数名要用单引号来包裹. 值是包括选项的对象:选项包括有三

  • python数字类型和占位符详情

    目录 一.数据类型 1.数据类型的判断 2.float浮点型 3.bool布尔型 4.complex复数类型 5.转义字符 6.格式化字符串 一.数据类型 1.数据类型的判断 Number => int float complex bool 容器 => str list tuple set dict 判断类型: isinstance(数据,类型) => 如果是这个类型返回True , 否则返回False lst = [1,2,3] res = isinstance(lst,list) p

  • Python tkinter 列表框Listbox属性详情

    目录 1 属性 1.1 activestyle 1.2 background(bg) 1.3 borderwidth(bd) 1.4 cursor 1.5 disabledforeground 1.6 exportselection 1.7 font 1.8 foreground(fg) 1.9 height 1.10 highlightbackground.highlightcolor和highlightthickness 1.11 listvariable 1.12 relief 1.13

  • Java8新特性之类型注解_动力节点Java学院整理

    注解从java5开始加入这一特性,发展到现在已然是遍地开花,在很多框架中得到了广泛的使用,用来简化程序中的配置.那充满争议的类型注解究竟是什么?复杂还是便捷? 什么是类型注解 在java 8之前,注解只能是在声明的地方所使用,比如类,方法,属性:java 8里面,注解可以应用在任何地方,比如: 创建类实例 new @Interned MyObject(); 类型映射 myString = (@NonNull String) str; implements 语句中 class Unmodif

  • php 7新特性之类型申明详解

    前言 PHP7 将类型申明变成了可能,PHP 7 支持的形参类型申明的类型有以下几种 整型 浮点型 字符串型 布尔类型 函数形参与返回类型声明demo 如下 /** * @author 袁超 <yccphp@163.com> */ class Demo{ /** * int $name 则是形参类型声明 * : int 是返回类型声明 */ public function age(int $age) : int { return $age; } } 上面我们定义了一个 Demo 类,里面就一个

随机推荐