C# 中的记录(record)类型和类(class)类型对比总结

简介: C# 中的记录(record)类型和类(class)类型对比总结

前言

大家都知道,类(class)和接口(interface)是面向对象开发的两大支柱,但是在 C# 9.0,.NET 团队又引入了一种全新的类型: 记录(record)类型。它究竟是何方神圣?跟传统的类(class)类型有什么区别?它会颠覆面向对象的开发方式吗?……

今天我们来聊聊这个记录(record)类型,及它跟类(class)类型的区别和联系。

记录(record)类型是什么?

记录(record)类型是 .NET 团队设计出来的一个用于定义不可变数据模型的特殊类型。

所谓不可变,就是说,这个类型一旦被创建,它里面的属性的值就不能被修改。

所有属性、成员变量都为只读的类型叫作 "不可变类型",不可变类型可以简化程序逻辑,并且可以减少并发访问、状态管理等麻烦。

它如何定义呢?来看一个简单的记录(record)类型的使用例子。

一个简单的例子

在这个例子里,定义了一个 Person 记录类型,和两个不可变属性:FirstNameLastName

using System;
// 定义一个记录(record)类型,及两个不可变属性
public record Person(string FirstName, string LastName);
class Program
{
    static void Main()
    {
        // 创建一个 Person 实例
        var person1 = new Person("Jacky", "Yang");
    
    // 以下语句会出错,不可以在运行时改变 person1 对象的属性值
    // person1.FirstName = "Meng";
        
        // 打印对象
        Console.WriteLine(person1); 
    // 结果: Person { FirstName = Jacky, LastName = Yang }
        // 创建与 person1 相同的新实例
        var person2 = person1 with { };
        // 检查相等性
        Console.WriteLine(person1 == person2); 
    // 输出: True
        // 创建一个新对象,修改属性
        var person3 = person1 with { LastName = "Smith" };
        Console.WriteLine(person3); 
    // 输出: Person { FirstName = John, LastName = Smith }
    }
}

优势

从上面的例子可以看到,记录(record)类型具有这些优势:

  1. 记录(record)类型的定义非常简单,仅需 1 行代码
  2. 两个相同属性的记录(record)类型对象可以直接比较是否相等
  3. 记录(record)类型的属性只能在定义时赋值,不能在运行中改变,这种不可变性,可以有效防止意外的数据更改,减少错误;在多线程环境中,也会带来更高的性能。

跟类(class)类型的区别

  1. 不可变性
  • record 默认是不可变的,这意味着一旦创建了一个 record 实例,就不能修改它的属性值。
  • class 没有这样的限制,你可以随意更改其属性值。
  1. 默认的构造函数
  • record 有一个默认的参数化(为所有属性赋值)的构造函数,可以通过初始化器设置所有属性值。
  • class 没有这样的默认构造函数,需要显式声明。
  1. 值相等:
  • record 通过值比较来判断两个对象是否相等,这意味着两个具有相同属性值的 record 实例会被认为是相等的。
  • class 则是通过引用比较来判断相等性,如果要判断同一个类型的两个实例对象是否相等,需要通过重写 Equals 方法、重写 == 运算符等来解决这个问题,总之需要编写非常多的额外代码,比较麻烦。
  1. 解构
  • record 支持解构,可以直接拆分出其成员。
  • class 需要显式实现解构逻辑。

应用场景

  • 数据传输对象(DTO): 在 ASP.NET Web API 开发中,记录(record)类型很适合用来定义 DTO 数据模型,因为记录(record)类型天然具有 DTO 数据模型所要求的简单、不可变、轻量级、容易序列化等特性
  • 领域模型: 在领域驱动设计中,记录(record)类型非常适合用来表示值对象
  • 其他: 其它要求确保实体状态不变的业务场景

本质

记录(record)类型有这么多优点,看起来它跟类(class)类型有很多不同,但相似之处更多,它的本质究竟是什么呢?

反编译上面例子生成的程序集,可以看到,编译器把记录(record)类型的 Person 类型编译成一个 Person 类,并且提供了构造方法、属性、ToString方法、Equals方法等,所以记录(record)类型的本质其实依然是一个类(class)类型,它并没有颠覆面向对象的开发方式,它实际上只是一个语法糖

以下是记录(record)类型反编译后的主要代码:

public class Person : IEquatable<Person>
{
   public string FirstName { get; set /*init*/; }
   public string LastName { get; set /*init*/; }
   public Person(string FirstName, string LastName)
   {
      this.FirstName = FirstName;
      this.LastName = LastName;
   }
   public override string ToString()
   {
      //省略代码
   }
   public virtual bool Equals(Person? other)
   {
      //省略代码
    }
}

注意事项

  1. 既然记录(record)类型本质也是一个类(class)类型,就意味它也可以定义可变属性,但这是不建议的,因为这跟记录(record)类型的设计理念相冲突,如果需要频繁修改的状态,class 类型更为合适。
  2. 对于记录(record)类型的更新,需要谨慎设计以避免破坏向后兼容性

总结

记录(record)类型提供了为所有属性赋值的构造方法,所有属性都是只读的,对象之间可以进行值的相等性比较,并且编译器为类型提供了可读性强的 ToString 方法。

它结合了值类型和引用类型的特性,特别适合用来表示那些不可变的数据结构,比如数据传输对象(DTO)或领域模型中的值对象。

在需要编写不可变类并且需要进行对象值比较的时候,使用记录(record)类型可以把代码的编写难度大大降低。

记录(record)类型相比类(class)类型,有很多不同的地方,但它本质上也是一个类(class)类型,这也意味着它可以做到类(class)类型可以做的事情,所以在使用时尤其要注意其边界,在正确的场景中使用它,才能有化腐朽为神奇的效果。

我是老杨,一个执着于编程乐趣、至今奋斗在一线的 10年+ 资深研发老鸟,是软件项目管理师,也是快乐的程序猿,持续免费分享全栈实用编程技巧、项目管理经验和职场成长心得。欢迎关注老杨的公众号,相互交流,共同进步!


相关文章
|
1月前
|
开发框架 .NET C#
C#|.net core 基础 - 删除字符串最后一个字符的七大类N种实现方式
【10月更文挑战第9天】在 C#/.NET Core 中,有多种方法可以删除字符串的最后一个字符,包括使用 `Substring` 方法、`Remove` 方法、`ToCharArray` 与 `Array.Copy`、`StringBuilder`、正则表达式、循环遍历字符数组以及使用 LINQ 的 `SkipLast` 方法。
|
2月前
|
存储 C# 索引
C# 一分钟浅谈:数组与集合类的基本操作
【9月更文挑战第1天】本文详细介绍了C#中数组和集合类的基本操作,包括创建、访问、遍历及常见问题的解决方法。数组适用于固定长度的数据存储,而集合类如`List<T>`则提供了动态扩展的能力。文章通过示例代码展示了如何处理索引越界、数组长度不可变及集合容量不足等问题,并提供了解决方案。掌握这些基础知识可使程序更加高效和清晰。
80 2
|
18天前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
27 3
|
17天前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
34 1
|
16天前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
9 0
|
1月前
|
C#
C# 可空类型(Nullable)
C# 单问号 ? 与 双问号 ??
46 12
|
1月前
|
Java 程序员 C#
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
12 0
|
2月前
|
C# 数据安全/隐私保护
C# 一分钟浅谈:类与对象的概念理解
【9月更文挑战第2天】本文从零开始详细介绍了C#中的类与对象概念。类作为一种自定义数据类型,定义了对象的属性和方法;对象则是类的实例,拥有独立的状态。通过具体代码示例,如定义 `Person` 类及其实例化过程,帮助读者更好地理解和应用这两个核心概念。此外,还总结了常见的问题及解决方法,为编写高质量的面向对象程序奠定基础。
25 2
|
3月前
|
程序员 C#
C# 语言类型全解
C# 语言类型全解
25 0
|
3月前
|
Java C# 索引
C# 面向对象编程(一)——类
C# 面向对象编程(一)——类
34 0