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

2023-10-27

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 RecordPerson()
 {
  Name = "Tom",
  Age = 12,
 };
 Console.WriteLine(p1);
 
 var p2 = p1 with { Age = 10 };
 Console.WriteLine(p2);
 
 var p3 = new RecordPerson() { Name = "Tom", Age = 12 };
 Console.WriteLine(p3);
 Console.WriteLine($"p1 Equals p3 =:{p1 == p3}");
 
 RecordPerson2 p4 = new("Tom", 12);
 Console.WriteLine(p4);
}

这里的示例,用 record 声明了两个 model,第二个 model 声明的时候使用了简化的写法,

record RecordPerson2(string Name, int Age); 这样的声明意味着,构造方法有两个参数,分别是 string Name 和 int Age,并对应着两个属性,属性的声明方式和 RecordPerson 一样 public string Name { get; init; } 都是一个 get 一个 init

对于 record 支持一个 with 表达式,来修改某几个属性的值,这对于有很多属性都相同的场景来说是及其方便的,来看一下上面示例的输出结果
在这里插入图片描述

What inside
那么 record 内部发生了什么呢,我们来反编译看一下,我们看一下使用 DnSpy 反编译的结果

RecordPerson

private class RecordPerson : IEquatable<RecordSample.RecordPerson>
{
 // Token: 0x17000007 RID: 7
 // (get) Token: 0x06000027 RID: 39 RVA: 0x000025F4 File Offset: 0x000007F4
 [Nullable(1)]
 protected virtual Type EqualityContract
 {
  [NullableContext(1)]
  [CompilerGenerated]
  get
  {
   return typeof(RecordSample.RecordPerson);
  }
 }
 
 // Token: 0x17000008 RID: 8
 // (get) Token: 0x06000028 RID: 40 RVA: 0x00002600 File Offset: 0x00000800
 // (set) Token: 0x06000029 RID: 41 RVA: 0x00002608 File Offset: 0x00000808
 public string Name
 {
  [CompilerGenerated]
  get
  {
   return this.<Name>k__BackingField;
  }
  [CompilerGenerated]
  set
  {
   this.<Name>k__BackingField = value;
  }
 }
 
 // Token: 0x17000009 RID: 9
 // (get) Token: 0x0600002A RID: 42 RVA: 0x00002611 File Offset: 0x00000811
 // (set) Token: 0x0600002B RID: 43 RVA: 0x00002619 File Offset: 0x00000819
 public int Age
 {
  [CompilerGenerated]
  get
  {
   return this.<Age>k__BackingField;
  }
  [CompilerGenerated]
  set
  {
   this.<Age>k__BackingField = value;
  }
 }
 
 // Token: 0x0600002C RID: 44 RVA: 0x00002624 File Offset: 0x00000824
 public override string ToString()
 {
  StringBuilder stringBuilder = new StringBuilder();
  stringBuilder.Append("RecordPerson");
  stringBuilder.Append(" { ");
  if (this.PrintMembers(stringBuilder))
  {
   stringBuilder.Append(" ");
  }
  stringBuilder.Append("}");
  return stringBuilder.ToString();
 }
 
 // Token: 0x0600002D RID: 45 RVA: 0x00002678 File Offset: 0x00000878
 [NullableContext(1)]
 protected virtual bool PrintMembers(StringBuilder builder)
 {
  builder.Append("Name");
  builder.Append(" = ");
  builder.Append(this.Name);
  builder.Append(", ");
  builder.Append("Age");
  builder.Append(" = ");
  builder.Append(this.Age.ToString());
  return true;
 }
 
 // Token: 0x0600002E RID: 46 RVA: 0x000026EA File Offset: 0x000008EA
 [NullableContext(2)]
 public static bool operator !=(RecordSample.RecordPerson r1, RecordSample.RecordPerson r2)
 {
  return !(r1 == r2);
 }
 
 // Token: 0x0600002F RID: 47 RVA: 0x000026F6 File Offset: 0x000008F6
 [NullableContext(2)]
 public static bool operator ==(RecordSample.RecordPerson r1, RecordSample.RecordPerson r2)
 {
  return r1 == r2 || (r1 != null && r1.Equals(r2));
 }
 
 // Token: 0x06000030 RID: 48 RVA: 0x0000270C File Offset: 0x0000090C
 public override int GetHashCode()
 {
  return (EqualityComparer<Type>.Default.GetHashCode(this.EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.<Name>k__BackingField)) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(this.<Age>k__BackingField);
 }
 
 // Token: 0x06000031 RID: 49 RVA: 0x0000274C File Offset: 0x0000094C
 [NullableContext(2)]
 public override bool Equals(object obj)
 {
  return this.Equals(obj as RecordSample.RecordPerson);
 }
 
 // Token: 0x06000032 RID: 50 RVA: 0x0000275C File Offset: 0x0000095C
 [NullableContext(2)]
 public virtual bool Equals(RecordSample.RecordPerson other)
 {
  return other != null && this.EqualityContract == other.EqualityContract && EqualityComparer<string>.Default.Equals(this.<Name>k__BackingField, other.<Name>k__BackingField) && EqualityComparer<int>.Default.Equals(this.<Age>k__BackingField, other.<Age>k__BackingField);
 }
 
 // Token: 0x06000033 RID: 51 RVA: 0x000027B0 File Offset: 0x000009B0
 [NullableContext(1)]
 public virtual RecordSample.RecordPerson <Clone>$()
 {
  return new RecordSample.RecordPerson(this);
 }
 
 // Token: 0x06000034 RID: 52 RVA: 0x000027B8 File Offset: 0x000009B8
 protected RecordPerson([Nullable(1)] RecordSample.RecordPerson original)
 {
  this.Name = original.<Name>k__BackingField;
  this.Age = original.<Age>k__BackingField;
 }
 
 // Token: 0x06000035 RID: 53 RVA: 0x000027D9 File Offset: 0x000009D9
 public RecordPerson()
 {
 }
 
 // Token: 0x0400000C RID: 12
 [CompilerGenerated]
 [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 private readonly string <Name>k__BackingField;
 
 // Token: 0x0400000D RID: 13
 [CompilerGenerated]
 [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 private readonly int <Age>k__BackingField;
}

从上面的反编译结果可以看的出来,record 其实就是一个 class,只是编译器会帮我们做一些事情,编译器帮我们做了哪些事呢?

实现了基于属性值的相等性比较,不再使用默认的引用,并且重写了 ==/!= operator 和 GetHashCode
为了方便调试,重写了 ToString 方法,也提供了 PrintMembers 方法来实现比较方便只显示某些比较重要的参数实现了 EqualityContract 方法来指定c#教程类型比较的类型,默认是当前类型实现了 $ 方法和一个特殊的构造方法,用来克隆一个对象,相当于 record 帮我们实现了一个浅复制 的 原型模式,还是强类型的,这个方法在代码里不能直接调用,当我们使用 with 表达式的时候,编译器会调用这个方法,并对某些属性进行赋值
再来看一下 RecordPerson2

private class RecordPerson2 : IEquatable<RecordSample.RecordPerson2>
{
 // Token: 0x06000036 RID: 54 RVA: 0x000027E2 File Offset: 0x000009E2
 public RecordPerson2(string Name, int Age)
 {
  this.Name = Name;
  this.Age = Age;
  base..ctor();
 }
 
 // Token: 0x1700000A RID: 10
 // (get) Token: 0x06000037 RID: 55 RVA: 0x000027F9 File Offset: 0x000009F9
 [Nullable(1)]
 protected virtual Type EqualityContract
 {
  [NullableContext(1)]
  [CompilerGenerated]
  get
  {
   return typeof(RecordSample.RecordPerson2);
  }
 }
 
 // Token: 0x1700000B RID: 11
 // (get) Token: 0x06000038 RID: 56 RVA: 0x00002805 File Offset: 0x00000A05
 // (set) Token: 0x06000039 RID: 57 RVA: 0x0000280D File Offset: 0x00000A0D
 public string Name
 {
  [CompilerGenerated]
  get
  {
   return this.<Name>k__BackingField;
  }
  [CompilerGenerated]
  set
  {
   this.<Name>k__BackingField = value;
  }
 }
 
 // Token: 0x1700000C RID: 12
 // (get) Token: 0x0600003A RID: 58 RVA: 0x00002816 File Offset: 0x00000A16
 // (set) Token: 0x0600003B RID: 59 RVA: 0x0000281E File Offset: 0x00000A1E
 public int Age
 {
  [CompilerGenerated]
  get
  {
   return this.<Age>k__BackingField;
  }
  [CompilerGenerated]
  set
  {
   this.<Age>k__BackingField = value;
  }
 }
 
 // Token: 0x0600003C RID: 60 RVA: 0x00002828 File Offset: 0x00000A28
 public override string ToString()
 {
  StringBuilder stringBuilder = new StringBuilder();
  stringBuilder.Append("RecordPerson2");
  stringBuilder.Append(" { ");
  if (this.PrintMembers(stringBuilder))
  {
   stringBuilder.Append(" ");
  }
  stringBuilder.Append("}");
  return stringBuilder.ToString();
 }
 
 // Token: 0x0600003D RID: 61 RVA: 0x0000287C File Offset: 0x00000A7C
 [NullableContext(1)]
 protected virtual bool PrintMembers(StringBuilder builder)
 {
  builder.Append("Name");
  builder.Append(" = ");
  builder.Append(this.Name);
  builder.Append(", ");
  builder.Append("Age");
  builder.Append(" = ");
  builder.Append(this.Age.ToString());
  return true;
 }
 
 // Token: 0x0600003E RID: 62 RVA: 0x000028EE File Offset: 0x00000AEE
 [NullableContext(2)]
 public static bool operator !=(RecordSample.RecordPerson2 r1, RecordSample.RecordPerson2 r2)
 {
  return !(r1 == r2);
 }
 
 // Token: 0x0600003F RID: 63 RVA: 0x000028FA File Offset: 0x00000AFA
 [NullableContext(2)]
 public static bool operator ==(RecordSample.RecordPerson2 r1, RecordSample.RecordPerson2 r2)
 {
  return r1 == r2 || (r1 != null && r1.Equals(r2));
 }
 
 // Token: 0x06000040 RID: 64 RVA: 0x00002910 File Offset: 0x00000B10
 public override int GetHashCode()
 {
  return (EqualityComparer<Type>.Default.GetHashCode(this.EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.<Name>k__BackingField)) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(this.<Age>k__BackingField);
 }
 
 // Token: 0x06000041 RID: 65 RVA: 0x00002950 File Offset: 0x00000B50
 [NullableContext(2)]
 public override bool Equals(object obj)
 {
  return this.Equals(obj as RecordSample.RecordPerson2);
 }
 
 // Token: 0x06000042 RID: 66 RVA: 0x00002960 File Offset: 0x00000B60
 [NullableContext(2)]
 public virtual bool Equals(RecordSample.RecordPerson2 other)
 {
  return other != null && this.EqualityContract == other.EqualityContract && EqualityComparer<string>.Default.Equals(this.<Name>k__BackingField, other.<Name>k__BackingField) && EqualityComparer<int>.Default.Equals(this.<Age>k__BackingField, other.<Age>k__BackingField);
 }
 
 // Token: 0x06000043 RID: 67 RVA: 0x000029B4 File Offset: 0x00000BB4
 [NullableContext(1)]
 public virtual RecordSample.RecordPerson2 <Clone>$()
 {
  return new RecordSample.RecordPerson2(this);
 }
 
 // Token: 0x06000044 RID: 68 RVA: 0x000029BC File Offset: 0x00000BBC
 protected RecordPerson2([Nullable(1)] RecordSample.RecordPerson2 original)
 {
  this.Name = original.<Name>k__BackingField;
  this.Age = original.<Age>k__BackingField;
 }
 
 // Token: 0x06000045 RID: 69 RVA: 0x000029DD File Offset: 0x00000BDD
 public void Deconstruct(out string Name, out int Age)
 {
  Name = this.Name;
  Age = this.Age;
 }
 
 // Token: 0x0400000E RID: 14
 [CompilerGenerated]
 [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 private readonly string <Name>k__BackingField;
 
 // Token: 0x0400000F RID: 15
 [CompilerGenerated]
 [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 private readonly int <Age>k__BackingField;
}
RecordPerson2 相比 RecordPerson 的区别在于构造器不同:

看上面反编译的结果,可以看出:

RecordPeron2 和 RecordPerson 都声明了两个属性,都是 public string Name { get; init; }/public int Age { get; init; }
RecordPerson 的构造方法是无参构造方法,而 RecordPerson2 的构造方法是 RecordPerson2(string Name, int Age)
多出来一个 Deconstruct 方法,使得我们可以比较方便的解析一个对象的值,可以参考下面这个示例
1
2
3
4
foreach (var (name, age) in new[] { p4 })
{
 Console.WriteLine($"{name}={age}");
}

再来看一下测试方法的反编译结果:

RecordSample.RecordPerson p = new RecordSample.RecordPerson
{
 Name = "Tom",
 Age = 12
};
Console.WriteLine(p);
RecordSample.RecordPerson recordPerson = p.<Clone>$();
recordPerson.Age = 10;
RecordSample.RecordPerson p2 = recordPerson;
Console.WriteLine(p2);
RecordSample.RecordPerson p3 = new RecordSample.RecordPerson
{
 Name = "Tom",
 Age = 12
};
Console.WriteLine(p3);
Console.WriteLine(string.Format("p1 Equals p3 =:{0}", p == p3));
RecordSample.RecordPerson2 p4 = new RecordSample.RecordPerson2("Tom", 12);
Console.WriteLine(p4);

这里主要可以看到 with 表达式的实现,其实就是调用 $ 方法复制了一个对象,并修改指定的属性值

More
record 实现了基于值的相等性比较,并且实现了 原型模式,可以比较方便的创建一个新的值完全相等的对象,这对于有一些业务场景来说是非常适合使用 record 来代替原来的实现的

Reference
https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9
https://github.com/WeihanLi/SamplesInPractice/tree/master/CSharp9Sample
https://github.com/WeihanLi/SamplesInPractice/blob/master/CSharp9Sample/RecordSample.cs

以上就是C# 9 新特性——record的相关总结的详细内容

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • Qt程序的发布

    QT相关技术问题 一 QT程序的发布 1 在程序中运行debug或者release 占用较少的内存 之后 复制release中的exe文件 放在新的文件夹中 比如 我的第一个QtAPP发布 2 部署环境设置 部署依赖 找到安装目录下的win

随机推荐

  • Lattice学习总结中……

    1 Lattice官网 http www latticesemi com 2 需要注册一下 sign in 按要求填写一下 3 注册完之后 在官网的首页 Products 下载Lattice的设计环境 ispLEVER classic 4
  • C++ STL --哈希表

    目录 1 unordered系列关联式容器 1 1 unordered map 1 1 1 unordered map的文档介绍 1 1 2 unordered map的接口说明 1 2 unordered set 1 3 在线OJ 2 底
  • java grid动态行合并,CSS Grid布局:合并单元格布局

    CSS Grid布局 网格单元格布局 一文中通过一些简单的实例介绍了如何给容器定义网格 并且怎么使用网格线或者网格区域来实现单元格这样的简单的布局 在文章结尾之处也提到过 这样的单元格如同表格一样 仅仅一个个独立的单元格是无法满足一些复杂的
  • nestjs 优秀的ORM框架sequelize操作数据库

    奉上最新代码 nestjs服务demo代码 gitee地址 github地址 nodejs的ORM sequelize 笔者在使用koa2开发后端服务的时候用的ORM框架是sequelize 觉得挺好用的 也做了分享 node从入门到放弃系
  • 安卓psp模拟器哪个好_更完美!安卓PSP模拟器PPSSPP 0.9.9发布

    PConline 资讯 最好的安卓PSP模拟器PPSSPP 0 9 9新版发布下载了 PPSSPP是最强的PSP模拟器 在PC 安卓和iOS上均有对应版本 笔者曾经简单介绍过PPSSPP安卓版的用法 详情可以点此查看PPSSPP教程 现在
  • HiveSQL:求累计访问量

    数据 userId visitDate visitCount u01 2017 1 21 5 u02 2017 1 23 6 u03 2017 1 22 8 u04 2017 1 20 3 u01 2017 1 23 6 u01 2017
  • 10个常见的Redis面试"刁难"问题

    导读 在程序员面试过程中Redis相关的知识是常被问到的话题 作为一名在互联网技术行业打击过成百上千名的资深技术面试官 本文作者总结了面试过程中经常问到的问题 十分值得一读 作者简介 钱文品 老钱 互联网分布式高并发技术十年老兵 目前任掌阅
  • docker搭建测试(项目)管理平台jira

    1 下载镜像 使用docker下载jira和mysql的镜像 docker pull cptactionhank atlassian jira software docker pull mysql 5 6 docker images 查看是
  • Font shape `OMX/cmex/m/n‘ in size <10.53937> not available (Font) size <10.95> substituted.

    Latex在写公式时 报如下错误 Font shape OMX cmex m n in size lt 10 53937 gt not available Font size lt 10 95 gt substituted 解决方案 在 b
  • Web指纹识别技术研究与优化实现(CMS)

    本文通过分析web指纹的检测对象 检测方法 检测原理及常用工具 设计了一个简易的指纹搜集脚本来协助发现新指纹 并提取了多个开源指纹识别工具的规则库并进行了规则重组 开发了一个简单快捷的指纹识别小工具TideFinger 并实现了一套在线的指
  • python爬虫实践-01-携程酒店评论的爬取

    0 关键 携程网其最大的特点就是 基本上所有的有效数据都是通过Ajax异步请求获取的 本博客的主要内容为 构造Ajax请求 获得返回的reviews数据 由于返回的数据为JSON格式 很好分析 判定是否爬完酒店评论 直接获取评论数目 想要通
  • Dorado下拉框多选(ListDropDown)

    最终样式如下图 这里是通过ListDropDown下拉框做出的效果 1 在ListDropDown的Entity属性添加下拉内容 并且设置红色框的属性为false 该控件的onClose事件 var value arg selectedVa
  • ESP32-C3 学习测试 蓝牙 篇(四、GATT Server 示例解析)

    了解了蓝牙 GATT 相关概念 趁热打铁 分析一下官方示例 GATT Server 的应用程序架构 目录 前言 一 GATT Server 示例分析 1 1 初始化 1 2 回调函数 gatts event handler gap even
  • 软件技术基础知识忏悔录C#&.NET篇(一)

    为何开始 人已是大三之年 虽是身在985 心里却没有半分985的底气 自从大二分流以来 自己几乎是没再系统的学过什么 除了几位知识还算渊博的老师教了较为详细的数据库 数据结构的知识之外 其他老师大抵都是迷迷糊糊的念些大家都知道的大条话 然后
  • 开源许可协议:GPL、LGPL、AGPL、MPL和BSD、MIT、Apache

    概述 一 开源许可证的分类 开源许可证分为2种类型 宽松型和著作权型 1 宽松型 Permissive 该类许可证往往只要求被许可方保留原作品的版权信息 对用户施加的限制较少 衍生软件可以成为私有软件 如Apache MIT BSD系列许可
  • 基于Session实现登录流程

    流程一 发送短信验证码 用户在提交手机号后 会校验手机号是否合法 如果不合法 则要求用户重新输入手机号 如果手机号合法 后台此时生成手机号对应的验证码 后台先得到验证码 同时将验证码保存到session中 然后再通过短信的方式 测试中输出到
  • 2020年网络安全国赛解析(仅个人想法)

    A 1任务一登录安全加固 请对服务器Log Web Data 按要求进行相应的设置 提高服务器的安全性 1 密码策略 Log Web Data a 最小密码长度不少于8个字符 windows 打开本地安全策略 开始菜单 管理工具 管理 本地
  • 关于nlohmann::json的简单使用

    nlohmann json的使用非常简单 只需要包含 hpp文件即可 这是它的官网https github com nlohmann json 简单使用 include json hpp include
  • Apache Doris (四) :Doris分布式部署(一) FE部署及启动

    目录 1 Apache Doris下载 2 节点划分 3 节点配置 4 FE部署及启动 进入正文之前 欢迎订阅专题 对博文点赞 评论 收藏 关注IT贫道 获取高质量博客内容 部署Apache Doris时需要分别部署FE BE Broker
  • C# 9 新特性——record的相关总结

    Intro C 9 中引入了 record record 是一个特殊类 用它来实现 model 在有些情况下会非常的好用 Sample record RecordPerson public string Name get init publ