struct赋值时逐字段拷贝,但嵌套引用类型仅复制引用;满足纯数据、≤16字节、不可变三条件才适合用;构造函数须初始化所有字段;未必在栈上,依宿主位置而定。

Struct 赋值时到底发生了什么?别被“值类型”三个字骗了
赋值时复制整个结构体内容,是 struct 最核心的行为特征——但这个“复制”不是万能的深拷贝。比如:
-
struct里有string字段?复制的是引用,不是字符串内容本身(string不可变,所以表现安全) -
struct里有个class类型字段?改副本里的该字段属性,原结构体里的字段也会跟着变(因为只复制了引用) - 你写
var s2 = s1;,看似简单,背后是逐字段拷贝——字段越多、越大,开销越明显
所以“值语义”只保证顶层字段独立,不保证嵌套对象隔离。真要完全隔离,得手动克隆或改用 record struct。
什么时候必须用 struct?看这三条硬指标
不是“小就用 struct”,而是满足全部三条才值得考虑:
- 逻辑上纯数据载体:比如
Point、Size、DateOnly,不封装行为,不维护状态 - 大小稳定 ≤ 16 字节:.NET 官方推荐阈值;超了反而因频繁复制拖慢性能(比如含大数组或长字符串)
- 设计为不可变或极少修改:每次赋值都在复制,高频修改等于高频内存搬运
反例:struct 里放 event、virtual 方法、IDisposable 实现——编译可能过,但语义错、性能差、维护地狱。
Class 和 Struct 构造函数写法差异在哪?编译器会卡你哪一步?
class 构造函数自由度高;struct 构造函数受编译器强约束:
-
struct不允许显式定义无参构造函数(C# 10+ 允许但仅限于init成员场景,日常应避免) - 每个带参构造函数里,必须显式初始化 所有字段,漏一个就编译失败
- 哪怕你只写了一个
public Point(int x),也得把Y赋上值,不能留空 -
struct总有隐式无参构造:声明Point p;就可用,p.X自动是0
常见错误:struct Bad { public int X; public Bad(int y) { } } → 编译报错:“未将字段 X 赋值”。
Struct 真的总在栈上吗?async、数组、类字段里它去哪了?
“struct 在栈上”只是局部变量的常见情况,不是铁律。它的实际位置取决于宿主:
- 局部变量(如
var s = new Point();)→ 通常在栈 - 作为
class的字段(如public Point Location { get; set; })→ 随 class 一起分配在堆 - 放在数组里(如
Point[] arr = new Point[100];)→ 整个数组在堆,每个Point是内联存储 - 出现在
async方法或迭代器中 → 可能被编译器提升到堆上的状态机对象里
这意味着:别指望 struct 一定规避 GC;在堆上大量存在时,它和 class 一样参与垃圾回收——只是没引用计数、没终结器而已。










