在 C# 中,struct(结构体)是一种值类型(Value Type),常用于表示轻量级的数据对象,例如坐标、日期、颜色、范围等。
很多初学者只知道"结构体在栈上,类在堆上",但实际上远不止这么简单。本文将从底层原理、内存模型、性能分析、适用场景、易错点与最佳实践多个角度深入讲解 C# 结构体,帮助你真正掌握 struct 的设计思想。
一、什么是结构体(Struct)?
结构体是一种值类型数据结构,用于组织一组相关数据。
csharp
struct Books
{
public string Title;
public string Author;
public string Subject;
public int BookId;
}
使用示例:
csharp
Books book1;
book1.Title = "C Programming";
book1.Author = "Nuha Ali";
book1.Subject = "C Programming Tutorial";
book1.BookId = 6495407;
Console.WriteLine(book1.Title);
二、Struct 与 Class 的本质区别(核心)
很多文章只讲"栈和堆",其实真正核心在于:
✅ 结构体是值语义
✅ 类是引用语义
我们从 6 个维度对比:
1️⃣ 值类型 vs 引用类型
结构体(值类型)
csharp
MyStruct s1 = new MyStruct(1, 2);
MyStruct s2 = s1; // 整体复制
s1.X = 100;
Console.WriteLine(s2.X); // 不受影响
👉 复制的是"整个对象"
类(引用类型)
csharp
MyClass c1 = new MyClass(1, 2);
MyClass c2 = c1; // 复制引用
c1.X = 100;
Console.WriteLine(c2.X); // 被影响
👉 复制的是"内存地址"
2️⃣ 内存分配
| 类型 | 存储位置 |
|---|---|
| struct | 通常在栈 |
| class | 堆上,对象引用在栈 |
⚠️ 注意:
- 如果结构体作为类的字段
- 或被装箱(Boxing)
- 或被捕获进闭包
那么它会进入堆内存。
所以:
❌ struct 一定在栈上(错误)
✅ struct 是值类型(正确)
3️⃣ 构造函数限制
struct 规则:
- ❌ 不能写无参构造函数(C# 10 之前)
- ✅ 必须为所有字段赋值
csharp
struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}
4️⃣ 继承与多态
| 能力 | struct | class |
|---|---|---|
| 继承 | ❌ 不支持 | ✅ 支持 |
| 多态 | ❌ 不支持 | ✅ 支持 |
| 实现接口 | ✅ 支持 | ✅ 支持 |
结构体不能作为基类。
5️⃣ 可空性
结构体是值类型:
csharp
Point p = null; // ❌ 错误
如果需要可空:
csharp
Point? p = null;
本质是:
csharp
Nullable<Point>
6️⃣ 默认可变性
结构体默认是可变的:
csharp
struct Point
{
public int X;
public int Y;
}
但更推荐:
csharp
readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
这样可以避免副本修改带来的问题。
三、结构体的性能优势与陷阱
✅ 什么时候 struct 更快?
- 小对象(小于 16 字节为佳)
- 不需要继承
- 不需要多态
- 频繁创建
- 数组大量存在
例如:
csharp
struct Point
{
public int X;
public int Y;
}
创建 100 万个 Point:
- struct:连续内存块
- class:100 万次堆分配 + GC 压力
差距巨大。
❌ struct 可能更慢的情况
1️⃣ 结构体太大
csharp
struct BigData
{
public int A1;
public int A2;
...
public int A20;
}
赋值:
csharp
BigData b2 = b1; // 复制 80 字节以上
频繁复制会导致性能下降。
2️⃣ 装箱(Boxing)
csharp
int x = 10;
object obj = x; // 装箱
结构体转换为 object 时会进入堆。
会产生:
- 内存分配
- GC 压力
- 性能下降
四、经典面试问题解析
❓ 为什么结构体不能有无参构造函数?
因为:
CLR 会自动为值类型提供默认初始化(字段清零)
csharp
Point p;
等价于:
csharp
Point p = default(Point);
字段自动初始化为:
- int → 0
- bool → false
- 引用类型 → null
❓ 为什么结构体构造函数必须初始化所有字段?
因为:
结构体必须始终处于"完全初始化状态"
否则会破坏值语义。
❓ 结构体可以包含引用类型吗?
可以:
csharp
struct Person
{
public string Name; // string 是引用类型
}
但:
- 结构体本身是值类型
- 内部字段可以是引用类型
五、结构体最佳实践
✅ 推荐使用 struct 的场景
- 表示一个"数据包"
- 不超过 16 字节
- 不需要继承
- 逻辑简单
- 不经常被修改
- 表示数学意义对象(Point、Vector)
❌ 不建议使用 struct 的场景
- 大对象
- 复杂业务模型
- 需要继承
- 需要多态
- 需要引用语义
六、现代 C# 推荐写法
推荐:
csharp
public readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
优点:
- 不可变
- 线程安全
- 不易出 bug
- 语义清晰
七、struct vs class 总结对比
| 对比点 | struct | class |
|---|---|---|
| 类型 | 值类型 | 引用类型 |
| 内存 | 栈/内联 | 堆 |
| 赋值 | 复制整个对象 | 复制引用 |
| 继承 | ❌ | ✅ |
| 默认可空 | ❌ | ✅ |
| 适用场景 | 轻量数据 | 复杂对象 |
| GC压力 | 小 | 较大 |
八、真正理解 struct 的一句话总结
struct 适合表示"值",class 适合表示"对象"。
值是:
- 一个数据
- 一个坐标
- 一个颜色
- 一个数学实体
对象是:
- 一个用户
- 一个订单
- 一个系统模块
- 一个有行为的实体
九、进阶建议
如果你想真正理解:
- 值语义
- GC 原理
- 装箱拆箱
- 栈与堆
- 内存布局
- 性能优化
建议深入学习:
- CLR 内存模型
- JIT 优化机制
- Span
- ref struct
- readonly struct
十、结语
结构体并不是"比类更高级",也不是"性能更强的类"。
它只是:
适用于表示小型数据的值类型工具。
用对场景,性能飞跃。
用错场景,性能灾难。
理解 struct,本质是理解:
- 值语义
- 内存复制
- GC 机制
- CLR 设计思想
如果你能真正掌握 struct 与 class 的差异,你的 C# 水平会提升一个层级。