值类型直接存数据,引用类型只存地址——这是所有行为差异的起点。
struct 和 class 的内存分配位置不同
局部变量声明的
struct实例(如
Point p = new Point(1, 2);)通常整个结构体数据都落在栈上;而
class实例(如
Person p = new Person();)对象本体一定在堆上,栈上只存一个 4 或 8 字节的引用(指针)。 例外:当
struct是某个
class的字段时,它会随类一起被分配在堆上(不是栈) 栈内存自动释放,无 GC 开销;堆内存由垃圾回收器管理,有延迟和不确定性 频繁创建小
struct(如
Vector2)可显著降低 GC 压力;但大
struct(比如超过 16 字节)栈拷贝开销反而升高
赋值时的行为差异:值拷贝 vs 引用拷贝
这是最常踩坑的地方——表面写法一样,底层语义完全不同。
struct赋值是深拷贝:
s2 = s1后修改
s2.X不会影响
s1.X
class赋值是浅拷贝:
c2 = c1后
c2和
c1指向同一块堆内存,改一个就等于改了另一个 传参也一样:把
struct传给方法,默认复制整块数据;
class传参只复制引用,方法内修改对象状态会反映到调用方
默认值、null 和构造函数约束
这些限制都源于“值类型必须始终有确定值”这一设计前提。
struct变量不能为
null(除非显式声明为
Point?),默认就是所有字段为零值(
X=0, Y=0)
class变量默认是
null,不 new 就用会抛
NullReferenceException
struct不允许定义无参构造函数(C# 10+ 允许
init成员但仍有字段初始化强制要求),所有字段必须在构造中赋值
class可自由定义任意构造函数,字段也能带默认值(
public int Count = 1;)
真正难的不是记住区别,而是判断什么时候该用哪个——别光看“小数据用 struct”,还要看是否会被装箱、是否跨线程共享、是否要作为泛型约束(
where T : struct)、甚至是否会被序列化成 JSON(某些库对 struct 的 null 处理更严格)。这些细节一漏,运行时表现就可能和预期南辕北辙。
