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. recordclassstruct 的本质区别

内存表现:引用还是值?

  • classrecord —— 都是引用类型。传给函数时,复制的是对象的引用。
  • structrecord 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 操作符

只有 recordrecord 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
如果你做的是像数字或坐标点那样的值对象——用 structrecord 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
1
调查/小测验
DTO的问题第 19 级,课程 4
不可用
DTO的问题
DTO、record 和 with
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION