record记录类型

编写程序的时候,有时候需要比较两个对象是否相等,C#中的==运算符默认判断两个变量指向的是否是同一个对象。

如果两个对象是同一种类型,并且所有属性完全相等,但是它们是两个不同的对象,导致==运算符的比较结果是false,则可以通过重写Equals方法、重写==运算符等来解决这个问题,不过这要求编写非常多的额外代码。

在C#9.0中增加了记录(record)类型的语法,编译器会自动生成Equals、GetHashcode等方法。

定义
C#
 public record Person(string FirstName,string LastName);

可以看到,上面的Person类型的定义前面写的是record,而不是熟悉的class或者interface。

Person类型中定义了FirstName和LastName两个属性。

==比较
C#
        Person person1 = new("Ichi", "Shan");
        Person person2 = new("Zack", "Yang");
        Person person3 = new("Ichi", "Shan");
        Console.WriteLine(person1);
        Console.WriteLine(person1==person2);
        Console.WriteLine(person1 ==person3);
        Console.WriteLine(person1.FristName);
结果

编译器会根据Person类型中的属性定义,自动为Person类型生成包含全部属性的构造方法。

默认情况下,编译器会生成一个包含所有属性的构造方法,因此,new Person()、new Person(“Ichi”)这两种写法都是不可以的。

编译器同样会为record类型生成ToString方法和Equals方法等。

让我们来看一下反编译后的主干内容
C#
  public class Person : IEquatable<Person>
  {
      public Person(string firstName, string lastName)
      {
         this.FirstName = firstName;
         this.LastName = lastName;
      }

      public string FirstName { get; set /*init*/; }
      public string LastName { get; set /*init*/; }

      public override string ToString()
      {
          throw new NotImplementedException();
      }
      public virtual bool Equals(Person? other)
      {
          throw new NotImplementedException();
      }
  }

可以看到,编译器确实把record类型的Person类型编译成一个Person类,并且提供了构造方法、属性、ToString方法、Equals方法等。

因此record类型编译后仍然只是一个普通的类,record是编译器提供的一个语法糖。

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

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

当然,record类型的定义是比较灵活。

实现部分属性只读而部分属性可以读写的效果
C#
  //实现
  public record Person(string LastName)
  {
      public string FirstName { get; set; }

      public void SayHello()
      {
          Console.WriteLine($"Hello,我是{LastName}{FirstName}");
      }
  }
  //调用
   Person p1 = new("Shan");
   Person p2 = new("Shan");
   Console.WriteLine(p1);
   Console.WriteLine(p2);
   Console.WriteLine(p1==p2);
   p1.FristName = "Ichi";
   p1.SayHello();
   Console.WriteLine(p1==p2);
   Console.WriteLine(p1);
输出结果

可以看到,Person类型的LastName属性仍然是用record类型语法定义的只读属性,编译器为Person类型生成了包含为LastName属性赋值的构造方法。

而FirstName属性是用传统的语法定义的普通属性,这个属性是可读可写的。

在record类型中,也可以为类型提供多个构造方法,从而提供多种创造对象的途径。
C#
 public record User(string UserName,string? Email,int Age)
 {
     public User(string UserName,int Age) : this(UserName, null, Age)
     {

     }
 }

上面的User类型中声明了3个属性:不可为空的string类型的UserName属性,可为空的string类型的Email属性,以及Age属性。

编译器自动生成一个包含这3个属性的构造方法,同时提供了一个为UserName的Age赋值的构造方法,这个构造方法通过this关键字调用编译器默认的构造方法完成对象的初始化。

调用不同的构造方法
C#
   User u1 = new("Ichi", 19);
   User u2 = new("Ichi", "ichi@example.com", 23);

record类型的对象的属性默认都是只读的,而且我们也推荐使用属性都为只读的类型。

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

With关键字简化创建
C#
  User u1 = new("Ichi", 19);
  User u2 = new(u1.UserName, "ichi@example.com", u1.Age);
  //使用with关键字
  User u3 = u2 with { Email = "ichi@qq.com" };
  Console.WriteLine(u3);
  Console.WriteLine(u2);
  Console.WriteLine(object.ReferenceEquals(u2,u3));
输出结果

可以看到,在第2行代码中,使用with关键字创建了u2对象的一个副本,其他属性都复制自u2对象,只有Email属性采用不同的值。

订阅评论
提醒
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
滚动至顶部