c#中struct赋值是逐字节复制所有字段,形成完全独立副本;若含引用类型字段(如list),仅复制引用地址,不深拷贝对象本身,导致共享修改风险。

struct 是值类型,不是“轻量 class”,它天然不支持引用语义——这是所有行为差异的根源,别从“性能快慢”出发理解它。
struct 赋值时到底发生了什么
赋值操作(s2 = s1)会把 s1 所有字段的值逐字节复制一份给 s2,两个变量完全独立。哪怕结构体里嵌套了 string 或 class 类型字段,也只是复制那个引用值(即地址),不是深拷贝对象本身。
- 如果结构体含
string字段,赋值后s1.field和s2.field指向同一字符串实例(因为string不可变,通常无感知) - 如果结构体含
List<int></int>字段,s1.list.Add(1)会同时影响s2.list—— 因为复制的是引用,不是列表副本 - 没有隐式“深拷贝”逻辑;想安全隔离,得手动克隆或改用
readonly struct+ 不可变设计
构造函数必须显式初始化所有字段
C# 要求每个非无参构造函数执行结束时,结构体的每一个实例字段都已被明确赋值。编译器不会帮你补默认值(哪怕字段是 int 或 bool)。
- 错误写法:
public MyStruct(int x) { X = x; }(若结构体还有Y字段但没赋值,编译失败) - 正确写法:
public MyStruct(int x, int y) { X = x; Y = y; }或使用字段初始值设定项 + 显式构造函数 - C# 10+ 允许写
public MyStruct() { },但内部仍需确保所有字段被赋值,否则报错 - 自动属性(
public int X { get; set; })在旧版本 C# 中受限,因编译器生成的后备字段名不可见,无法在构造函数中显式赋值
struct 不能继承,但可以实现接口
结构体不支持类的继承链,也不允许 protected 成员、virtual 方法或抽象成员。但它能实现一个或多个接口,用于统一行为契约。
- 接口方法在结构体内必须是
public,且不能加override(除非是显式接口实现) - 调用接口方法时,若结构体是局部变量,会 boxing(装箱)成引用类型再调用——这会触发堆分配,破坏值语义优势
- 推荐用
ref readonly参数传入结构体,或让接口方法签名接受in T参数,避免意外装箱 - 常见误用:把
IComparable<t></t>实现写在大结构体上,结果每次排序都引发大量装箱
什么时候该用 struct 而不是 class
核心判断标准只有一条:这个类型是否**逻辑上代表一个值**,且**大小可控、生命周期短、不常被多处共享修改**。
- 适合:
Point、DateTimeOffset、Vector3、Guid、小数据包如HttpStatusCode—— 它们像数字一样被比较、传递、复制 - 不适合:
Person、Order、任何含集合字段(List<t></t>、Dictionary<k></k>)或需要事件/虚方法的类型 - 大小建议:单个 struct 实例 ≤ 16 字节较安全;超过 32 字节就该警惕复制开销和栈溢出风险
- 容易被忽略的一点:struct 作为类的字段时,它实际存在堆上(随宿主对象一起分配),此时“栈 vs 堆”的说法已不成立,重点应回归值语义是否合理











