CodeGym /课程 /C# SELF /认识 record 和位置语法

认识 record 和位置语法

C# SELF
第 19 级, 课程 1
可用

1. 数据传递的问题

假设我们有个学校的应用,要在不同模块之间传递学生的信息:名字、出生年份、班级。一般咋搞?

我们通常会写个 class,像这样:


public class Student
{
    public string Name { get; set; }
    public int YearOfBirth { get; set; }
    public string Class { get; set; }
}
存学生数据的 class(老办法)

看着挺正常。但这种写法有几个坑:

  • 要比较两个学生是不是一样,默认只会比对象引用。也就是说,两个字段都一样但对象不同的学生——不相等!
  • class 创建后还能被改,这有时候会出 bug(尤其是对象已经被别的地方用的时候);
  • 一堆“模板”代码:构造函数、比较方法、复制(克隆)方法,ToString 等等。

你可能已经猜到了:C# 可以帮我们省掉这些麻烦!隆重介绍——record。

2. record 是啥?

record 是 C# 里专门为存数据设计的一种特殊类型。record 有两个核心特性:

  1. 不可变性(immutability): record 类型的对象默认是不可变的,也就是说属性值只能在创建时设定,之后不能改(准确说,set 是私有的)。当然你也可以声明可变的 record,但默认是不可变的。
  2. 值比较: 如果两个 record 对象所有字段的值都一样,它们就被认为是相等的(== 和 .Equals() 的行为不一样了!)。

其实 record 就是用来在应用层之间传递数据的神器(比如从数据库到 controller,从 controller 到 view 等等)。

3. record 的语法

最简单的写法——位置语法

如果只是想传一组值,可以用一行代码声明类型:


public record Student(string Name, int YearOfBirth, string Class);
record 的位置语法

底层发生了啥?编译器会自动帮你生成:

  • 只读的自动属性(set 是私有的);
  • 带所有参数的构造函数;
  • 比较和复制方法;
  • 超好用的 ToString,输出格式很友好!

怎么用位置 record

来,咱们在学校应用里用下这个新类型:

var student1 = new Student("伊万", 2008, "8A");
var student2 = new Student("玛丽亚", 2008, "8B");

访问属性和以前一样(只是不能改):

Console.WriteLine($"{student1.Name}, {student1.YearOfBirth}, {student1.Class}");

试图创建后改属性

student1.Name = "彼得"; // 报错!属性是只读的。

如果你把上面那行取消注释,编译器立马就会报错:只读属性不能赋值。

自动生成的 ToString 长这样

Console.WriteLine(student1); // 输出:Student { Name = 伊万, YearOfBirth = 2008, Class = 8A }

不用手动格式化也很清楚!

4. record 对象的比较

提醒一下:如果用传统 class 创建两个内容一样的对象,它们还是不相等的:

var a = new Student("伊万", 2008, "8A");
var b = new Student("伊万", 2008, "8A");

Console.WriteLine(a == b); // 对于 class:false

但如果 Student 是 record,比较就和你想的一样了:


public record Student(string Name, int YearOfBirth, string Class);

var a = new Student("伊万", 2008, "8A");
var b = new Student("伊万", 2008, "8A");

Console.WriteLine(a == b); // 对于 record:true!
record 对象的值比较

也就是说,两个字段一样的 record 就算是内存里两个对象,也会被认为是相等的。

5. record 的底层实现

很多同学会惊讶,record 编译器帮我们做了多少事。来对比下,如果用 class 手写要写多少代码,record 又帮我们省了多少。

手写的传统 class

public class Student
{
    public string Name { get; }
    public int YearOfBirth { get; }
    public string Class { get; }

    public Student(string name, int yearOfBirth, string @class)
    {
        Name = name;
        YearOfBirth = yearOfBirth;
        Class = @class; // 指向自己的 class
    }

    public override bool Equals(object? obj)
    {
        if (obj is not Student other) return false;
        return Name == other.Name && YearOfBirth == other.YearOfBirth && Class == other.Class;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Name, YearOfBirth, Class);
    }

    public override string ToString()
    {
        return $"Student {{ Name = {Name}, YearOfBirth = {YearOfBirth}, Class = {Class} }}";
    }
}

难怪程序员都快神经质了——同样的代码要写十遍!

record——一行搞定


public record Student(string Name, int YearOfBirth, string Class);
record——一行就全搞定!

6. record 和不可变性:能干啥,不能干啥

record 默认属性只读,这能帮你避免一堆 bug。但如果你非要(比如对接很老的 API),也能声明可变的 record:

public record MutableStudent
{
    public string Name { get; set; }
    public int YearOfBirth { get; set; }
    public string Class { get; set; }
}

现在这些字段可以改了,但你会失去一部分优势(比如安全性)。

7. record 的解构

因为位置语法很像 tuple,所以 record 也能很方便地解构:


var student = new Student("伊万", 2008, "8A");

var (name, year, className) = student;

Console.WriteLine($"{name} - {year}, {className}"); // 伊万 - 2008, 8A
位置 record 的解构

编译器会为每个位置 record 自动生成 Deconstruct 方法,这让你用 LINQ、switch 模式啥的都很爽。

8. record 是 class 还是 struct?

默认情况下 record 是引用类型,和 class 一样。也就是说,引用类型的所有特性(堆上存储、引用复制等)都适用。

如果你想要值类型(value type),C# 也有办法——可以写成这样:


public record struct Point(int X, int Y);
record struct——值类型的 record

但传递数据时基本都用经典的 record,也就是引用类型。关于 record struct 的细节——下节课再聊 :P

class、struct、record 对比

类型 默认不可变 值比较 易解构 自动 ToString
class 否 否(按引用) 否 否
struct 否 是 否 否
record 是 是 是 是

9. 特点和常见错误

很多新手用 record 会搞混。有的人以为改了一个 record 对象的字段,另一个也会变(像 class 的引用复制)。不会! 还有,写 with 的时候,记住总是创建副本,不会改原对象。record 超适合需要数据纯净、可预测的业务逻辑。

对了,如果你用 init 而不是 set 声明字段,这些字段也只能在创建时或者用 with 赋值,之后就不能改了。

2
任务
C# SELF, 第 19 级, 课程 1
已锁定
按值比较 record
按值比较 record
2
任务
C# SELF, 第 19 级, 课程 1
已锁定
record 解构
record 解构
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION