应根据语义选择:需值语义、小而固定、无共享状态用struct;需引用语义、可扩展、生命周期管理或复杂行为用class。赋值与传参行为不同,struct复制值,class复制引用,误用易引发bug。

选 struct 还是 class,不是看“哪个更高级”,而是看语义是否匹配:需要值语义、小而固定、无共享状态,就用 struct;需要引用语义、可扩展、有生命周期管理或复杂行为,必须用 class。
赋值和传参时行为完全不同
这是最常踩坑的点——你以为在改副本,其实改了原对象,或者反过来。
-
struct赋值(s2 = s1)或作为方法参数传入时,会完整复制所有字段。修改s2.X不会影响s1.X -
class赋值(c2 = c1)只是复制引用地址。修改c2.X会同步反映到c1.X - 即使方法参数声明为
ref或in,struct仍可能因装箱(比如转object或接口)意外触发堆分配,带来 GC 压力和性能抖动
构造函数和字段初始化限制很硬
struct 的构造逻辑受编译器强约束,稍不注意就编译失败。
-
struct不允许定义无参构造函数(C# 10+ 允许但仅限于显式调用,且不能省略字段初始化) - 所有字段必须在每个构造路径中被显式赋值,包括
public字段和自动属性的 backing field(public int X { get; set; }算作未初始化,需在构造函数里写X = 0) -
class可自由定义无参/有参构造,字段还能带默认值(public int X = 42;),struct不允许字段初始值设定项 - 声明即初始化:
MyStruct s;合法,且s.X为0;MyClass c;合法,但c为null
继承、多态和内存位置不可混用
试图让 struct 承担类的职责,很快会撞墙。
-
struct是隐式sealed,不能被继承,也不能继承其他class或struct;只支持实现接口(但接口调用会触发装箱) -
class支持完整的继承链、虚方法、抽象成员、protected访问修饰符;struct不允许virtual、abstract、override(除重写ValueType方法外) -
struct实例通常在栈上,但嵌套在class中、作为泛型集合元素、或被装箱后,就会跑到堆上——此时它失去“栈快”的优势,还多了装箱开销 -
class总是在堆上,由 GC 管理;struct没有析构函数,也不参与 GC 生命周期
大小和场景选择有经验阈值
没有绝对标准,但超过几个关键数字,struct 就大概率变成负优化。
- 推荐上限:16 字节以内(如
Point { int X, Y; }是 8 字节)。超过这个大小,传参/赋值的复制成本可能抵消栈分配优势 - 避免含引用类型字段的
struct(如string、object、class实例),它会让结构体实际占用更大内存,且破坏“纯值”语义 - 高频创建/销毁的小对象(如数学计算中的向量、颜色)适合
struct;需要事件、资源持有(文件句柄、数据库连接)、异步状态机的对象必须用class -
Nullable<t></t>底层就是struct,所以int?能为null——但这只是语法糖,struct本身仍不可空
真正难的不是记住规则,而是判断“这个类型到底该不该被共享”。一旦你发现两个变量本应独立却意外联动,或者 profiling 显示大量装箱/GC,大概率是 struct 用错了地方,或者该换 class 了。











