record 你现在用上了吗?

news/2025/1/6 11:10:10/文章来源:https://www.cnblogs.com/chenwolong/p/18602030

. 为什么会有这篇文章

record 是 C# 9.0 的主打特性之一。距离它发布快 3 年了,却没怎么见过有人使用它。虽然我现在不写代码了,但是我还是抽空学习(问 GPT-4)了一下。记录一下,分析一下它在什么场景下使用,顺便分析一下我认为它为什么这么受冷落。

1. record 是什么

record 类型是一种不可变的引用类型,提供了值语义,便于开发者创建不可变的对象。

Record 类型主要包括以下特性:

  • 不可变性:record 类型的对象在创建后无法更改其状态。这意味着它的属性和字段都是只读的。
  • 值语义:record 类型提供了值语义,这意味着在比较两个 record 对象时,它们会根据它们的属性值进行比较,而不是根据引用。这使得它们在使用时更接近于原始数据类型,如 int 和 double。
  • 简洁的语法:record 类型支持 init-only 属性,可以在对象初始化时设置属性值。此外,C# 也提供了简洁的语法来声明 record 类型,称为 positional record。
  • 继承:record 类型可以继承自其他 record 类型,但必须保持不可变性。
  • 解构:record 类型支持解构功能,可以将对象的属性值解构为单独的变量。

以下是一个简单的 record 类型示例:

record Point(int X, int Y);

 

在这个例子中,我们定义了一个名为 Point 的 record 类型,它包含两个属性 X 和 Y。使用这种简洁的语法,编译器将自动生成相应的构造函数、属性访问器以及 EqualsGetHashCode 等方法。

要创建一个新的 Point 对象,可以使用以下语法:

var point = new Point(3, 4);

 

因为 record 类型是不可变的,所以在创建对象后,我们不能更改其属性值:

point.X = 5; // 这将导致编译错误

 

2. record 适合在什么场景下使用?

record 类型非常适合用于表示不可变的数据结构,特别是那些需要进行值比较而不是引用比较的场景。以下是一些典型的使用 record 的场景:

  1. 数据传输对象(DTO):在 ASP.NET Core 的 Web 后端开发中,数据传输对象(DTO)通常用于在不同的应用程序层之间传递数据。由于 DTO 主要负责携带数据,而不包含业务逻辑,因此使用 record 类型非常合适。它们可以简化对象创建和保证对象不可变性,同时提供了值语义,使得比较和传递变得简单。
record UserDto(int Id, string Name, string Email);

 

  1. 领域事件:在领域驱动设计(DDD)中,领域事件表示在业务领域中发生的重要事件。领域事件通常需要不可变性,以确保在传播过程中它们的状态不会被修改。record 类型非常适合用于表示领域事件。
record OrderCreatedEvent(int OrderId, DateTime CreatedAt);

 

  1. 值对象:值对象是另一个在 DDD 中经常出现的概念,它表示一个具有特定业务含义的简单对象,例如货币、地址或日期范围等。值对象应该是不可变的,并具有值语义,因此 record 类型非常适合用于实现值对象。
record Money(decimal Amount, string Currency);

 

  1. 函数式编程:由于 record 类型具有不可变性,它们非常适合用于函数式编程范式。在函数式编程中,函数是纯粹的,这意味着它们不会产生副作用,并且对于相同的输入,总是返回相同的输出。使用 record 类型可以帮助确保数据不可变性,降低错误发生的可能性。

总之,record 类型在需要保证数据不可变性、简化对象创建和传递、以及实现值语义时非常有用。这使得它们非常适合在 DTO、领域事件、值对象等场景中使用。

3. record 就是 class 吗?

我认为严格来说,record 就是 class。

下面这段代码是节选了一段这个 record record Point(int X, int Y); 的预编译后的结果,特别长,就大体过一遍就行,看看它是如何定义的,以及实现了哪些方法:

internal class Point : IEquatable<Point>
{private readonly int <X>k__BackingField;private readonly int <Y>k__BackingField;protected virtual Type EqualityContract{get{return typeof(Point);}}public int X{get{return <X>k__BackingField;}init{<X>k__BackingField = value;}}public int Y{get{return <Y>k__BackingField;}init{<Y>k__BackingField = value;}}public Point(int X, int Y){<X>k__BackingField = X;<Y>k__BackingField = Y;base..ctor();}public override string ToString(){StringBuilder stringBuilder = new StringBuilder();stringBuilder.Append("Point");stringBuilder.Append(" { ");if (PrintMembers(stringBuilder)){stringBuilder.Append(' ');}stringBuilder.Append('}');return stringBuilder.ToString();}protected virtual bool PrintMembers(StringBuilder builder){RuntimeHelpers.EnsureSufficientExecutionStack();builder.Append("X = ");builder.Append(X.ToString());builder.Append(", Y = ");builder.Append(Y.ToString());return true;}public static bool operator !=(Point left, Point right){return !(left == right);}public static bool operator ==(Point left, Point right){return (object)left == right || ((object)left != null && left.Equals(right));}public override int GetHashCode(){return (EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(<X>k__BackingField)) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(<Y>k__BackingField);}public override bool Equals(object obj){return Equals(obj as Point);}public virtual bool Equals(Point other){return (object)this == other || ((object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<int>.Default.Equals(<X>k__BackingField, other.<X>k__BackingField) && EqualityComparer<int>.Default.Equals(<Y>k__BackingField, other.<Y>k__BackingField));}public virtual Point <Clone>$(){return new Point(this);}protected Point(Point original){<X>k__BackingField = original.<X>k__BackingField;<Y>k__BackingField = original.<Y>k__BackingField;}public void Deconstruct(out int X, out int Y){X = this.X;Y = this.Y;}
}

 

可以看到,record 这个单词已经消失了,Point 的声明变成了 internal class Point,也就是 class。我觉得我们可以认为 record 就是 class,不过多了一些额外的功能。

4. record 与 class 和 struct 有什么区别?

record、class 和 struct 都是 C# 中用于定义自定义数据类型的关键字。他们之间的主要区别如下:

  1. 不可变性:record 类型是不可变的,这意味着在创建 record 对象之后,无法更改其属性值。而 class 和 struct 对象的属性值在创建后可以更改(除非显式标记为只读)。

  2. 引用类型 vs 值类型:class 是引用类型,这意味着当您将一个 class 对象赋值给另一个变量时,实际上是复制引用,而不是对象本身。因此,修改其中一个变量会影响另一个变量。而 struct 是值类型,这意味着在赋值时会复制整个对象。record 类型是引用类型,但具有值语义,这意味着它们在比较时具有与值类型类似的行为。

  3. 值语义:record 类型具有值语义,这意味着在比较两个 record 对象时,它们会根据它们的属性值进行比较,而不是根据引用。而 class 和 struct 的比较方式不同。class 类型在比较时使用引用语义,而 struct 类型使用值语义。

  4. 继承:record 和 class 类型都支持继承,而 struct 类型不支持继承。

  5. 构造函数和析构函数:class 类型可以有显式的构造函数和析构函数,而 struct 类型仅支持显式的构造函数,不支持析构函数。record 类型支持构造函数,但不支持析构函数。

  6. 解构:record 类型支持解构功能,可以将对象的属性值解构为单独的变量。而 class 和 struct 类型默认不支持解构,需要手动实现解构方法。

总之,record 类型是不可变的引用类型,具有值语义,主要适用于表示不可变的数据结构。class 类型是可变的引用类型,适用于表示具有复杂行为和状态的对象。struct 类型是可变的值类型,适用于表示轻量级的、简单的数据结构。

5. record 与 class 和 struct 在底层(内存层面)有什么区别呢?

在内存层面上,record、class 和 struct 之间的区别主要涉及到引用类型与值类型的不同表现以及与垃圾回收相关的行为。

  1. 引用类型与值类型:class 和 record 都是引用类型,它们的实例存储在托管堆上。当你将引用类型的实例分配给另一个变量时,实际上复制的是引用(内存地址),而不是对象本身。因此,两个变量指向的是同一个内存地址,修改其中一个变量的内容会影响另一个变量。而 struct 是值类型,它的实例通常存储在栈上(但在某些情况下,例如作为类的成员时,也可能存储在堆上)。值类型在赋值时会复制整个对象,因此修改一个变量的内容不会影响另一个变量。

  2. 垃圾回收:由于 class 和 record 类型的实例存储在托管堆上,它们受到 .NET 垃圾回收器(GC)的管理。垃圾回收器会自动检测不再使用的对象,并释放其内存。相对地,值类型(struct)在栈上分配的内存通常在方法返回时自动回收,不受垃圾回收器管理。这意味着 struct 类型在某些情况下可能具有更高的性能,因为它们不会引发垃圾回收操作。需要注意的是,当值类型作为引用类型的成员或存储在托管堆上时,它们仍然受到垃圾回收器的管理。

  3. 装箱与拆箱:由于 struct 是值类型,当将 struct 实例分配给 object 类型或实现的接口类型时,会发生装箱(boxing)操作,即将值类型封装在引用类型的对象中。在需要将引用类型对象还原为值类型时,会发生拆箱(unboxing)操作。这些操作在运行时可能导致性能损失。而对于 class 和 record 类型,不存在装箱和拆箱操作。

  4. 虽然 record 类型具有值语义,但在内存层面上,它们仍然作为引用类型进行处理。这意味着 record 类型的实例存储在托管堆上,并受到垃圾回收器的管理。与 class 类型相比,record 类型的主要区别在于它们的不可变性和值语义。在选择使用 record、class 还是 struct 时,应根据所需的语义和性能特性来权衡。

6. 为什么 record 如此被冷落?

record 类型似乎鲜有应用,也很少听到人们讨论它。为什么它会遭到冷落?这背后反映出一个值得关注的问题——对代码质量的忽视。

在现今竞争激烈的市场环境下,确保软件产品的快速迭代和交付显得尤为重要。但与此同时,有些公司在追求开发速度时,忽略了代码质量的重要性。一些开发者为了迅速完成任务,往往采用快速但不稳定的解决方案,从而导致代码可维护性和可读性降低,甚至增加了潜在的安全隐患。

而 record 类型正是为了提高代码质量而设计的。通过使用 record,我们可以简化代码,提高代码的安全性、可预测性和易于维护性。但如果我们不重视代码质量,那么 record 类型的优势将无法发挥,甚至可能被完全忽略。

7. 重视代码质量,让 record 发挥作用!

“不可变性”一定程度上与“代码质量”有关系,可参考文章 谁是你的菜?IEnumerable、IQueryable 和 ICollection 选择指南:https://cat.aiursoft.cn/post/2023/3/10/a-guide-to-ienumerable-iqueryable-and-icollection 中提到过“返回集合类型时应选择 IReadOnlyCollection<T> 或它的子类”。

返回 IReadOnlyCollection<T> 或其子类作为只读集合类型确实是一个很好的做法,因为这有助于保持数据的不可变性,从而提高代码质量。不可变性有助于减少在多线程环境中的竞争条件,减少因错误修改而导致的 bug,并使代码更加安全、可预测和易于维护。

使用 record 类型与使用 IReadOnlyCollection<T> 的理念在某种程度上是相似的。record 类型的不可变性和值语义使得它们在表示数据时更加稳定和安全。record 类型提供了一种简单、安全的创建不可变对象的方法,可以帮助我们编写更加高质量的代码。

record 类型的优点在于:

  • 不可变性:record 类型的实例一旦创建,它的属性值就不再改变。这降低了出现 bug 的风险,尤其在多线程环境中,因为你无需担心其他线程意外地修改了对象的状态。

  • 值语义:record 类型具有值语义,这意味着在比较和复制 record 实例时,它们的行为类似于原始数据类型(如 int 和 double)。这使得在处理数据时更容易预测它们的行为。

  • 简洁的语法:record 类型通过简化 getter、setter、构造函数和 Equals 等方法的实现,使得我们能够用更少的代码来表示数据。这有助于提高代码的可读性和可维护性。

因此,在实际项目中,我们可以结合使用 record 类型和 IReadOnlyCollection<T> 等只读集合类型来构建更加安全、稳定和高质量的代码。例如,在表示领域事件、值对象或数据传输对象等场景中,record 类型和只读集合类型可以一起发挥作用,从而确保数据的不可变性,提高代码质量。

8. 但是谁在乎呢?

你是不是以为我会说:

优秀的代码质量是软件开发的基石。优秀的代码质量不仅能提高软件的稳定性和安全性,还有助于降低维护成本和提高开发效率。因此,我们应当对代码质量给予足够的重视,从而让像 record 这样的优秀特性发挥其应有的作用。

现在是时候反思我们在软件开发中的价值观了。我们应当关注并提高代码质量,努力创造出更安全、更稳定、更易维护的软件。让我们一起拥抱 record,以及其他有助于提高代码质量的优秀特性,为我们的软件开发事业注入新的活力!

让我们共同努力,打破对代码质量的忽视,让 record 类型在实际项目中大放异彩,为我们的编程实践带来更多美好的变革!

以前可能我会这样说,但现在谁在乎呢?没有人喜欢 record,没有人在乎 record 的感受!就像没有人在乎代码质量。自从 ChatGPT 这个产品出现之后,特别是 GPT-4 出现之后,感觉程序猿好像真的能被取代了。以后估计就靠它疯狂输出代码了。相信常常使用这些 AI 工具的朋友也能看出这篇文章中明显也有 GPT-4 的影子,它又快又好,大家都想想以后吃哪碗饭吧,谁还在乎什么 record。

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

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

相关文章

渠道管理智慧:与新华三谈数字化革新与共赢策略

智算智存全能担当 新华三集团作为数字化解决方案领导者,致力于成为客户业务创新、数字化转型值得信赖的合作伙伴。作为紫光集团旗下的核心企业,新华三通过深度布局"云-网-算-存-端"全产业链,不断提升数字化和智能化赋能水平。新华三拥有计算、存储、网络、5G、安全、…

RocksDB 内存超限问题剖析

在使用 RocksDB 存储引擎的过程中,有部分开发者遇到了内存使用超出预期的情况。本文针对这一问题展开了深入分析,并提出了相应的解决方案和优化建议,提升系统性能和稳定性。作者:来自 vivo 互联网服务器团队- Zeng Luobin在使用 RocksDB 存储引擎的过程中,有部分开发者遇到…

20242822《Linux内核原理与分析》第十二周作业

缓冲区溢出漏洞实验 一、实验简介 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况。这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段。这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写。 二、实验准备…

信而泰网络测试仪校准解决方案

一、影响仪表精度的因素 网络测试仪是用于对数据网络及其相关设备性能参数进行测试的仪表,可以模拟网络终端产生流量,进行网络性能测试,对网络状态进行实时监测,分析和统计。数字计量对于精准数据的网络测试仪来说是一剂强心针,它能促进网络测试仪的规范化、数据化、准确化…

京准电钟:计算机网络监控系统搭建NTP校时服务器

京准电钟:计算机网络监控系统搭建NTP校时服务器京准电钟:计算机网络监控系统搭建NTP校时服务器 京准电钟:计算机网络监控系统搭建NTP校时服务器 京准电钟官微——ahjzsz 我们都知道,对于监控设备来说,设备时间的统一和精准,是保障录像文件及日志可靠的关键。 虽然录像机、…

依靠 PROFINet 与 Modbus 协议让西门子 PLC 成功连接 RS485 接口变频器

在工业网络环境中,不同网络协议的沟通不畅是否曾让您倍感困扰?别担心,捷米特JM-RTU-PN 数据通讯模块横空出世,为您化解这一棘手难题!此模块专注于 PROFINet 网络与Modbus 网络间的数据交互,能够巧妙地将 RS485 网络接入西门子 PLC的PROFINet 网络架构之中,并且全面支持多…

广成CAN-485-西门子PLC调试

1,TBOX波特率500K, 2,信号解析

易基因:WGBS揭示Vpr蛋白在HIV-1感染中对CD4+ T细胞DNA甲基化变化的作用|项目文章

大家好,这里是专注表观组学十余年,领跑多组学科研服务的易基因。 全球约有3800万HIV-1感染者,每年新增感染者约150万。HIV-1主要攻击CD4+ T细胞,导致其耗竭,最终发展为艾滋病和死亡。尽管抗逆转录病毒治疗有效抑制HIV-1复制,但由于病毒在潜伏感染细胞中的持续存在,这种病…

pandas中的groupby函数分组函数和 mean 求平均值函数

import pandas as pd #建一个集合有两列 一列是班级列;第二列是成绩列 data = {class: [A, A, B, B, B],score: [80, 90, 70, 85, 95]} # 使用熊猫库的DateFrame类 将上面两列数据 二维结构化 复制给df df = pd.DataFrame(data) #对df调用分组方法groupby,对其再取平均值 结果…

文案课程

促销要写的明显 -不要恶趣味 -谐音可以用于写文案 -数字描述文案是有必要的,清晰明显

转载:【AI系统】模型压缩基本介绍

随着神经网络模型的复杂性和规模不断增加,模型对存储空间和计算资源的需求越来越多,使得部署和运行成本显著上升。模型压缩的目标是通过减少模型的存储空间、减少计算量或提高模型的计算效率,从而在保持模型性能的同时,降低模型部署的成本。模型压缩的目标可以概括为以下几…

转载:【AI系统】SqueezeNet 系列

本文将介绍 SqueezeNet 系列网络,在轻量化模型这个范畴中,Squeezenet 是最早的研究。主要针对了一些组件进行轻量化。与以往的网络都只讲网络如何设计不同。SqueezeNext 则从硬件角度分析如何加速,从而更全面地了解网络结构的设计。 SqueezeNet 模型 SqueezeNet:是轻量化主…