核心区别
| 值类型 | 引用类型 | |
|---|---|---|
| 关键字 | struct enum int ... |
class record(默认) interface string ... |
| 存哪里 | 通常栈 | 堆(变量是栈上的指针) |
| 赋值 | 复制 | 复制引用 |
| 默认值 | 0 / false / null字段 | null |
| 可继承 | 不可 | 可 |
例子
struct Point { public int X, Y; }
class Person { public string Name = ""; }
var p1 = new Point { X = 1, Y = 2 };
var p2 = p1; // 复制
p2.X = 99;
Console.WriteLine(p1.X); // 1(p1 没变)
var a = new Person { Name = "Alice" };
var b = a; // 复制引用
b.Name = "Bob";
Console.WriteLine(a.Name); // Bob(同一个对象)
内置类型分类
// 值类型
int long short byte sbyte uint ulong ushort
float double decimal
bool char
struct enum
// 引用类型
class object string interface delegate
array (T[])
注意 string 是引用类型但表现像值类型——不可变,==/Equals 比较内容(特殊优化)。
装箱 / 拆箱
int n = 42;
object o = n; // 装箱:值类型 → 堆上的 object
int m = (int)o; // 拆箱:必须显式
装箱有性能代价——分配堆内存 + GC 压力。热路径里避免:
// ❌ 隐式装箱
ArrayList list = new ArrayList(); // 老 API,存 object
list.Add(42); // int → object,装箱
// ✅ 泛型避免
List<int> list2 = new List<int>();
list2.Add(42); // 不装箱
所有泛型集合 List<T> Dictionary<K,V> 都不装箱——这是 C# 2.0 引入泛型最大的好处之一。
struct 的合理使用
什么时候用 struct?
- 数据小(一般 ≤ 16 字节)
- 不可变或近乎不可变
- 语义上是"值"(坐标、颜色、向量、Money)
// ✅ 适合
readonly struct Vec2
{
public readonly double X, Y;
public Vec2(double x, double y) { X = x; Y = y; }
public Vec2 Add(Vec2 other) => new(X + other.X, Y + other.Y);
}
// ❌ 不适合:大、可变、有继承感
struct Order // 几十个字段
{
public List<Item> Items; // 每次复制都共享引用,可变 list 在多个值副本里 → 混乱
...
}
默认用 class,确认是值语义再切 struct。
record(C# 9)
record class 是带"值语义"的引用类型——== 比较所有字段相等:
record class Point(int X, int Y);
var p1 = new Point(1, 2);
var p2 = new Point(1, 2);
Console.WriteLine(p1 == p2); // True(字段相等)
Console.WriteLine(ReferenceEquals(p1, p2)); // False(不同对象)
record struct(C# 10)—— 值类型 + 自动 == / GetHashCode:
record struct Point(int X, int Y);
详见 第 12 篇。
引用变量 vs 实际对象
Person? p = null; // 引用变量 p 指向 null
p = new Person(); // 创建对象,p 指向它
p = null; // 解除引用,对象等 GC
// 多个引用指同一对象
Person p1 = new Person { Name = "Alice" };
Person p2 = p1;
p2.Name = "Bob";
// p1.Name 也是 "Bob"
ref / out / in(按引用传值类型)
static void Inc(int x) { x++; } // 复制 x,外面没变
static void IncRef(ref int x) { x++; } // 按引用传
int n = 5;
IncRef(ref n);
Console.WriteLine(n); // 6
out:调用前不要求初始化,方法内必须赋值:
static bool TryDivide(int a, int b, out int result)
{
if (b == 0) { result = 0; return false; }
result = a / b;
return true;
}
if (TryDivide(10, 3, out var r)) Console.WriteLine(r);
in:只读引用(避免大 struct 复制):
static double Distance(in Vec2 a, in Vec2 b)
{
return Math.Sqrt((a.X-b.X)*(a.X-b.X) + (a.Y-b.Y)*(a.Y-b.Y));
}
比较
| 值类型 | 引用类型 | 说明 | |
|---|---|---|---|
== |
默认比值 | 默认比引用 | record 比所有字段 |
Equals(other) |
比值(要正确重写) | 默认比引用 | string、record 重写过 |
ReferenceEquals(a, b) |
永远比引用 | 永远比引用 | 引用相等 |
心智模型
- 基本类型 + 小值:值类型,传递不担心
- 业务对象 / 大数据:class 引用类型
- 不可变的数据载体:
record class(最现代选择) - 算法 + 性能敏感:
readonly struct/Span<T>/ ref
→ 下一篇 字符串