值类型(如struct)赋值时复制整个值,修改副本不影响原变量;引用类型(如class)赋值只复制引用,多个变量共享同一堆对象;string是不可变引用类型,Nullable仍是值类型。

值类型和引用类型不是“选哪个更好”,而是“用错地方就会出 bug”——比如修改一个 struct 字段却没生效,或误以为 class 实例是独立副本而引发共享状态问题。
struct 赋值时复制整个值,别指望改副本影响原变量
当你写 MyPoint p1 = new MyPoint(1, 2); MyPoint p2 = p1;,p2 是 p1 的完整内存拷贝。后续对 p2.x 的赋值,不会改变 p1.x。
- 常见错误现象:
struct方法里修改字段但调用后原变量不变(方法接收的是副本) - 正确做法:如果需要“就地修改”,用
ref参数,例如void Offset(ref MyPoint p, int dx) { p.x += dx; } - 性能影响:小结构(如
int、Vector2)拷贝快;大struct(超 16 字节)频繁传参会拖慢性能 - 注意:
struct不能继承其他struct或class,也不能被继承
class 实例赋值只复制引用,多个变量可能指向同一块堆内存
Person p1 = new Person("Alice"); Person p2 = p1; 后,p1 和 p2 指向同一个堆对象。改 p2.Name 就等于改 p1.Name。
- 常见错误现象:“为什么我只改了
list2[0],list1[0]也变了?”——因为两个 list 里存的是同一个class实例引用 - 使用场景:需要共享状态、生命周期较长、数据结构复杂(含集合、事件、外部资源)时用
class - 兼容性注意:
class默认可为null,访问前不判空容易抛NullReferenceException - GC 影响:大量短期
class实例会增加 GC 压力;值类型则无此问题
string 看似值类型,实为不可变引用类型
string 是引用类型,但设计上模拟值语义:每次拼接(如 s += "x")都生成新对象,旧引用不变。这让人误以为它是值类型。
- 关键区别:
string不支持ref修改内部字符(没有 public settable 字段),也不允许像struct那样直接改成员 - 陷阱:用
==比较两个string是内容比较(重载过),但比较两个class默认是引用比较,除非重写Equals() - 替代方案:若需可变文本操作,考虑
StringBuilder;若需轻量结构体语义,别用string,改用ReadOnlySpan或自定义struct
Nullable 是值类型的“null 容忍层”,不是引用类型
int? 是 Nullable 的语法糖,本质仍是值类型——它在栈上占 8 字节(4 字节 int + 1 字节 bool 标志),不是堆上的引用。
- 错误认知:“
int?可以为 null,所以它像 class”——不对,它只是编译器帮你包装了判空逻辑 - 装箱行为特殊:当
int?为null时,装箱结果是null(引用意义上的 null);非空时,装箱成Int32对象 - 与
class混用时注意:把int?传给接受object的方法,会发生装箱;而string传过去只是引用传递,无额外开销
最易被忽略的点:嵌套场景下内存位置不透明——struct 里包含 class 字段,该字段本身还是引用;class 里嵌套 struct 字段,该字段直接内联存储在堆对象体内。这时候“值/引用”的边界已经不是非黑即白,得看具体字段声明和初始化方式。











