CodeGym /课程 /C# SELF /record、class 和 struct 的区别

record、class 和 struct 的区别

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

1. 为什么要有 record

在 C#(还有整个 .NET)里,class(class)和 struct(struct)一直是主力。但它们各有短板。class 是引用类型、可变的,默认是按引用比较(极少数例外)。struct 是值类型(传递时会复制),默认按字节比较,而且大多数时候是可变的(直到有了 readonly struct)。

如果你想要一个简单、自然的数据记录,能方便地按值比较、快速克隆、又不用担心对象被谁改了——那就得自己造轮子,或者用 ValueTuple 甚至 System.Tuple 这种库。但这些都不够优雅、表达力也一般。

所以 C# 9 推出了 record —— 一种数据类型,声明超简洁,天生不可变,按值比较,专为数据而生。

2. 四种类型一览表

class struct record record struct
类别 引用类型 值类型 引用类型 值类型
可变性 默认可变 默认可变 默认不可变(init) 默认不可变(init)
比较方式 按引用(==) 按值(字段) 按值(fields/properties) 按值(fields)
克隆 只能手写 只能手写 自带支持(with) 自带支持(with)
继承 支持 不支持 支持 不支持
不可变性 要自己实现 要自己实现 实现超简单 实现超简单
语法 最啰嗦 简短 最短(位置型) 挺短的
集合里用法 按引用 复制 按引用 复制

可视化图解

+----------------+     +----------------+     +--------------------+
|    class       |     |    struct      |     |      record        |
+----------------+     +----------------+     +--------------------+
| Reference Type |     | Value Type     |     | Reference Type     |
| Mutable        |     | Mutable        |     | Immutable (init)   |
| == : Reference |     | == : By Fields |     | == : By Value      |
+----------------+     +----------------+     +--------------------+

3. record、class 和 struct 的本质区别

内存表现:引用还是值?

  • class 和 record —— 都是引用类型。传给函数时,复制的是对象的引用。
  • struct 和 record struct —— 值类型。总是按字节复制(除非你显式按引用传)。

class PointClass { public int X; public int Y; }
struct PointStruct { public int X; public int Y; }
record PointRecord(int X, int Y);
record struct PointRecordStruct(int X, int Y);

void Demo()
{
    var pc = new PointClass { X = 1, Y = 2 };
    var ps = new PointStruct { X = 1, Y = 2 };
    var pr = new PointRecord(1, 2);
    var prs = new PointRecordStruct(1, 2);

    ChangeY(pc);    // pc.Y 会变!
    ChangeY(ps);    // ps.Y 不会变——复制!
    ChangeY(pr);    // pr.Y 会变!
    ChangeY(prs);   // prs.Y 不会变——复制!
}

void ChangeY(dynamic p) { p.Y = 99; }

如果你觉得 dynamic 这里有点神奇——对,就是为了演示下,struct 传进去不会变,class 或 record(引用类型)会变。

比较:怎么判断两个对象相等?

  • class:按引用比较(== 只有同一个内存对象才 true),除非你重写了 Equals。
  • struct:默认按所有字段的值比较。
  • record:按主构造器里所有字段/属性的值比较。
class Foo { public int A; public int B; }
record Bar(int A, int B);

var foo1 = new Foo { A = 42, B = 1 };
var foo2 = new Foo { A = 42, B = 1 };
var bar1 = new Bar(42, 1);
var bar2 = new Bar(42, 1);

Console.WriteLine(foo1 == foo2); // False! 不是同一个对象
Console.WriteLine(bar1 == bar2); // True! 值一样

有趣的点:
record struct 更牛:它们也是“按值”比较,像普通 struct,但有 record 的语法和特性。

4. 不变性:谁能保证不被改?

来对比下对象的安全性:

  • class:默认很容易被改,除非你把所有字段都 readonly。
  • struct:同理,但你可以声明成 readonly struct,这样字段和属性都不能改。
  • record:一般字段都用 init 修饰,只能在构造器或对象初始化器(with)里赋值。这样数据传递更安全。
  • record struct:同理,可以写成 readonly record struct,值类型也能享受 record 的不变性福利。

record Person(string Name, int Age);

var p1 = new Person("阿列克谢", 23);
// p1.Age = 24; // 报错!只能 init

var p2 = p1 with { Age = 24 }; // OK!创建新副本,数据更新

如果你项目很大,有一堆 entity 对象,record 能帮你避免很多“谁改了字段导致全挂”的 bug。

5. 语法:声明长啥样,怎么不搞混


// class
public class Product
{
    public int Id { get; init; }
    public string Name { get; init; }
}

// struct
public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

// record
public record Product(int Id, string Name);

// record struct
public record struct Point(int X, int Y);
不同类型声明的语法对比

你看,record 类型写法最短。构造器、析构器、相等判断、ToString() 等等全都“开箱即用”。

语法 自带功能 能继承吗
class 几乎啥都没有 能
struct 几乎啥都没有 不能
record ToString, Equals, Deconstruct, with 能
record struct ToString, Equals, Deconstruct, with 不能

6. 克隆和 with 操作符

只有 record 和 record struct 有专门的 with 操作符,可以轻松复制对象并改部分属性。


record User(string Name, int Age);

var user1 = new User("伊琳娜", 28);
var user2 = user1 with { Age = 29 }; // user2.Name == "伊琳娜", user2.Age == 29

class(class)的话只能手写复制方法,漏掉字段就等着踩坑吧。

7. 继承:谁能继承谁

  • class:支持标准继承(类层级、虚方法、抽象啥的)。
  • struct:不支持(只能实现接口)。
  • record:支持继承,但有限制(只能 record 继承 record,不能和 class 混用)。
  • record struct:不支持,和普通 struct 一样。
record Animal(string Name);
record Dog(string Name, string Breed) : Animal(Name); // OK!

class Vehicle { }
class Car : Vehicle { } // OK!

// struct 不能继承 struct

继承细节下节课再聊 :P

8. 实际场景下怎么选类型

  • class:大对象、行为多、生命周期长、状态会变、有层级(比如业务逻辑、UI 组件)。
  • struct:小型值对象,追求极致复制速度、无 GC、最小开销(比如坐标、颜色、金额——适合快速复制)。
  • record:DTO(数据传输对象)、参数对象、配置参数、不可变状态、计算结果,适合按内容比较。
  • record struct:值类型,需要不可变和 record 行为,但又不想在堆上分配。

9. 常见错误和坑

有些程序员以为 record 就是“class 的替代品”。其实不是!如果你写的对象生命周期里状态会变——用 class。
如果你要按引用比较(比如单例模式、对象生命周期很重要)——用 class。
如果你做的是像数字或坐标点那样的值对象——用 struct 或 record struct。
如果你要不可变、易比较、经常在应用层间传递、存集合、日志、序列化的对象——选 record。

还要注意:如果你声明了 record class,属性全是只读,但里面有嵌套对象——只要嵌套字段本身是可变的,还是能被改。


record Student(string Name, List<int> Grades);

var s1 = new Student("安东", new List<int>() {5,5,5});
var s2 = s1 with { };

s2.Grades.Add(2); // 两个对象都“中招”了!s1.Grades == s2.Grades
2
任务
C# SELF, 第 19 级, 课程 4
已锁定
创建 record 并比较对象
创建 record 并比较对象
2
任务
C# SELF, 第 19 级, 课程 4
已锁定
使用 `with` 运算符写入数据
使用 `with` 运算符写入数据
1
调查/小测验
DTO的问题第 19 级,课程 4
不可用
DTO的问题
DTO、record 和 with
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION