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

image

前言

大家都知道,类(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 没有这样的限制,你可以随意更改其属性值。
  2. 默认的构造函数

    • record 有一个默认的参数化(为所有属性赋值)的构造函数,可以通过初始化器设置所有属性值。
    • class 没有这样的默认构造函数,需要显式声明。
  3. 值相等:

    • record 通过值比较来判断两个对象是否相等,这意味着两个具有相同属性值的 record 实例会被认为是相等的。
    • class 则是通过引用比较来判断相等性,如果要判断同一个类型的两个实例对象是否相等,需要通过重写 Equals 方法、重写 == 运算符等来解决这个问题,总之需要编写非常多的额外代码,比较麻烦。
  4. 解构

    • 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年+ 资深研发老鸟,是软件项目管理师,也是快乐的程序猿,持续免费分享全栈实用编程技巧、项目管理经验和职场成长心得。欢迎关注老杨的公众号,相互交流,共同进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值