02020202 .NET Core重难点知识02-记录类型、典型的record类型、高级的record类型

02020202 .NET Core重难点知识02-记录类型、典型的record类型、高级的record类型

1. 记录类型的引入

  • 在C# 9.0之前,比较两个对象是否相等,需要使用“==”来判断两个变量是否指向的是同一个对象。
    • 即使两个对象是同一个类型,并且所有属性完全相同,但它们是两个不同的对象,使用“==”比较结果任然是false。
    • 此时需要重写Equals方法,重写“==”运算符来解决这个问题,导致要编写非常多的代码。
  • 从C# 9.0开始,增加了记录(Record)类型的语法,此时编译器会自动生成Equals,GetHashcode等方法。

2. 典型的record类型的用法

// @1 在Person类型声明的时候定义了record
public record Person(string FirstName, string LastName); // 注意此处声明的不是构造函数,而是声明类型。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// @2 调用record类型的代码
Person p1 = new Person("Zack", "Yang");
Person p2 = new Person("Zack", "Yang");
Person p3 = new Person("Way", "Qin");

Console.WriteLine(p1); // → Person{ FirstName = Zack, LastName = Yang} @2.1
Console.WriteLine(p1 == p2) // → True @2.2
Console.WriteLine(p2 == p3) // → False @2.3
Console.WriteLine(p1.FirstName) // → Zack
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
总结:
1. Person类型声明不是使用class或interface。
2. Person类型中定义了FirstName和LastName两个属性。
3. 编译器会根据Person类型中的属性定义,自动生成一个包含所有属性的构造方法,此时注意如下事项。
3.1 使用new Person()是不可以的。
3.2 使用new Person("Yang")是不可以的。
4. 编译器同样会为record类型生成ToString方法和Equals方法等。
4.1 此时在@2.1处会输出对象的所有属性值。
4.2 此时在@2.2处、@2.3处会对对象的属性的值进行比较,而不是比较变量知否指向内存中同一个对象。
2.1 观察反编译record类型的Person类
  • 编译器把record类型的Person类编译成一个Person类,并且提供了构造方法、属性、ToStirng方法、Equals方法等。因此record类型编译后任然只是一个普通类,record是编译器提供的一个语法糖。
2.2 典型record类型的注意事项
  • 编译器会为类型生成一个包含所有属性的构造方法,在初始化对象的时候,它可以确保对象的所有属性都被赋值。
  • record所有的属性默认都是只读的,因此编写p1.FristName="Qin"修改属性的值是不可以的。
  • record为类型提供了可读性强的ToString方法。
  • 典型record类型在编写不可变类并且需要进行对象属性值比较的时候,降低了代码难度。

3. 高级的record类型的用法

3.1 为record类型增加成员
public record Person(String LastName)
{
	public string FirstName {get; set}
 	public void SayHello()
    {
		Console.WriteLine($"Hello,我是{LastName}{FirstName}");
    }
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
Person p1 = new Person("Yang");
Person p2 = new Person("Yang");
Console.WriteLine(p1); // → Person { LastName = Yang, FirstName = }
Console.WriteLine(p2 == p2); // → True
p1.FirstName = "Zack"; 
p1.SayHello(); // → Hello,我是Zack Yang 
Console.WriteLine(p1 == p2); // → False

说明:
1. Person类型的LastName属性任然是用record类型语法定义的只读属性,编译器为Person类型生成了一个包含LastName属性的构造方法。
2. 而FirstName使用传统的语法定义的普通属性,这个属性可读、可写。
3.2 为record类型提供额外的构造方法
public record User(string UserName, string? Email, int Age)
{
	public User(string userName, int age)
        : this(userName, null, age)
    {
		...
    }
}

说明:
1. User声明了3个属性:不可为空的UserName,可为空的Email属性,值类型的Age属性。此时编译器会自动生成包含这3个属性的构造方法。
2. 同时,我们自己编写了一个为userName和age赋值的构造方法。这个构造方法通过this关键字调用编译器默认的构造方法完成对象初始化。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
User u1 = new User("Zack", 18);
User u2 = new User("Zack", "abc@qq.com", 18); 

说明:所有属性、成员变量都为只读的类型叫做不可变类型,不可变类型可以简化程序逻辑,并且减少并发访问、状态管理等麻烦。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 手动创建record对象的副本
User u1 = new User("Zack", "abc@qq.com", 18); // @1 生成原对象u1
User u2 = new User(u1.UserName, "cde@qq.com", u1.Age); // @2 生成副本u2

说明:原对象与副本只有一个或少数几个属性改变。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 使用with关键字创建副本
User u1 = new User("Zack", "abc@qq.com", 18);
User u2 = u1 with(Email = "cde@qq.com"); // @3
Console.WriteLine(u2); // → User {UserName = Zack, Email = cde@qq.com, Age = 18}
Console.WriteLine(u1); // → User {UserName = Zack, Email = abc@qq.com, Age = 18}
Console.WriteLine(Object.ReferenceEquals(u1, u2)) // False

说明: 
1. 在@3处,使用with关键字创建了u1对象的一个副本u2,其它属性都复制自u1对象,只有Email属性采用不同的值。
2. with操作生成的u2对象是u1对象的一个副本,原有u1对象的Email属性值并没有改变。

结尾

书籍:ASP.NET Core技术内幕与项目实战

视频:https://www.bilibili.com/video/BV1pK41137He

著:杨中科

ISBN:978-7-115-58657-5

版次:第1版

发行:人民邮电出版社

※敬请购买正版书籍,侵删请联系85863947@qq.com※

※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※

posted @ 2025-08-24 10:49  qinway  阅读(33)  评论(0)    收藏  举报