应根据语义和规模选择struct或class:struct适用于轻量值类型(如vector2、color),大小建议≤16字节;class适用于需继承、虚方法、集合、共享状态等场景。

别用 struct 模拟 class 行为,也别用 class 扛小数据——错位使用会直接引发静默 bug 或性能抖动。
struct 赋值是复制整个内存块,改副本不影响原变量
这是最常被忽略的底层行为。当你写 MyPoint p1 = new MyPoint(1, 2); MyPoint p2 = p1;,p2 不是指向 p1 的引用,而是把 p1 占用的全部字节(比如 8 字节)原样拷贝一份到新位置。
- 常见错误现象:
void Move(MyPoint p) { p.X += 10; }调用后p1.X完全没变——因为传入的是副本,函数内修改的是栈上另一块内存 - 正确做法:需要就地修改时,显式用
ref参数:void Move(ref MyPoint p) { p.X += 10; },调用必须写Move(ref p1) - 注意:如果 struct 里包含
class类型字段(如public List<int> Items</int>),赋值时只复制该引用,Items本身仍是共享的——值语义只到第一层
class 赋值只复制引用地址,多个变量共享同一堆对象
写 Person p1 = new Person("Alice"); Person p2 = p1; 后,p1 和 p2 都只是栈上的指针,指向堆中同一个 Person 实例。
- 常见错误现象:
list1[0].Name = "Bob"导致list2[0].Name也变成 "Bob"——因为两个 list 存的都是同一个Person引用 - 避免共享的简单方式:手动克隆,比如用构造函数复制字段:
new Person(p1.Name, p1.Age);或实现ICloneable(但注意浅拷贝/深拷贝区别) - 关键陷阱:
==比较两个class默认是比地址(引用相等),不是比内容;而string是特例(重载了==做内容比较)
struct 不能有无参构造函数,且字段声明不能赋初值
编译器强制你显式初始化所有字段,哪怕你只想用默认值。这和 class 完全不同。
- 以下代码会编译失败:
struct Point { public int X = 0; public int Y = 0; }—— 字段初始值设定项在 struct 中非法 - 正确写法:只在构造函数里赋值:
struct Point { public int X; public int Y; public Point(int x, int y) { X = x; Y = y; } } - 注意:
new Point()仍合法,它会把所有字段设为默认值(X=0, Y=0),但这不是调用你的构造函数,而是编译器生成的“零初始化” - 如果你需要“可空”,用
Point?(即Nullable<point></point>),它仍是值类型,只是加了一层 null 标志位
什么时候该选 struct,什么时候必须用 class
判断依据不是“哪个更高级”,而是看语义和规模。
- 适合用 struct 的场景:
Vector2、Color、DateOnly、Guid这类逻辑上表示“单个值”的轻量结构;大小建议 ≤ 16 字节,否则传参拷贝开销明显 - 必须用 class 的场景:需要继承、需要虚方法、包含集合或事件、生命周期长或需跨线程共享状态;哪怕只有两个字段,只要可能被多处修改并期望同步,就该用 class
- 容易被忽略的细节:
struct无法被lock(因为锁的是引用,而 struct 拷贝后地址变了),要用class包一层再锁;async方法中捕获的 struct 局部变量会被装箱到堆,破坏栈分配优势











