====== C# 结构体 (Struct) 详解 ======
在 C# 中,**结构体 (Struct)** 是一种轻量级的数据类型。与类(Class)不同,结构体是**值类型 (Value Type)**,这使得它在特定的性能敏感场景下具有极大的优势。
===== 1. 什么是结构体? =====
结构体使用 ``struct`` 关键字定义。它通常用于封装包含少量相关变量的数据组,例如坐标点、颜色值或复数。
public struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public override string ToString() => $"({X}, {Y})";
}
===== 2. 结构体 vs 类 (Struct vs Class) =====
这是 C# 面试和架构设计中最常见的问题。核心区别在于**内存分配**和**赋值行为**。
^ 特性 ^ 结构体 (Struct) ^ 类 (Class) ^
| **类型种类** | **值类型** (Value Type) | **引用类型** (Reference Type) |
| **存储位置** | 通常在**栈 (Stack)** 上(或内联在其他对象中) | 总是分配在**托管堆 (Heap)** 上 |
| **赋值行为** | **复制值**。赋值给新变量时,会拷贝整个数据副本。 | **复制引用**。赋值给新变量时,两个变量指向同一个对象。 |
| **垃圾回收 (GC)** | **不涉及 GC**(随作用域自动释放),对 GC 压力极小。 | **需要 GC** 回收,频繁创建销毁会造成 GC 压力。 |
| **继承** | **不支持继承**(不能继承其他类或结构体),但可以实现**接口 (Interface)**。 | 支持完整的继承体系 (Inheritance)。 |
| **默认值** | 不能为 null(除非使用 Nullable,即 `int?`),默认值为所有字段归零。 | 默认为 null。 |
| **析构函数** | 不支持析构函数 (Finalizer)。 | 支持析构函数。 |
===== 3. 结构体 vs 枚举 (Struct vs Enum) =====
虽然它们都是值类型,但用途完全不同。
^ 特性 ^ 结构体 (Struct) ^ 枚举 (Enum) ^
| **本质** | 复合数据结构。可以包含字段、属性、方法、构造函数等。 | 命名常量的集合。底层通常只是一个整数 (int, byte 等)。 |
| **数据承载** | 用于存储**多个**相关联的数据(如 X 和 Y 坐标)。 | 用于表示**单一**的状态、选项或模式(如 Color.Red, Week.Monday)。 |
| **逻辑** | 可以包含业务逻辑(方法)。 | 不能包含方法(虽然可以使用扩展方法模拟)。 |
| **用途** | 描述一个“对象”或“实体”的轻量级表现。 | 描述一组有限的“选项”或“类别”。 |
**示例对比:**
* **Enum:** `GameState { Playing, Paused, GameOver }` (只表示状态)
* **Struct:** `PlayerState { int Health; Vector3 Position; }` (包含具体数据)
===== 4. 什么时候应该使用结构体? =====
微软官方建议,只有当类型满足以下**所有**条件时,才考虑使用结构体:
- **逻辑上表示单一值**:类似于基本类型(int, double),如坐标点、复数。
- **实例体积很小**:通常建议实例大小小于 **16 字节**。如果大于这个大小,复制值的开销可能会超过引用传递的开销。
- **不可变 (Immutable)**:结构体创建后,其字段值最好不要改变。
- **不需要频繁装箱 (Boxing)**:如果需要频繁将该类型转换为 `object` 或接口,装箱操作会抵消结构体的性能优势。
===== 5. 最佳实践:不可变性 (Immutability) =====
在使用结构体时,强烈建议将其设计为**不可变**的。因为结构体是值拷贝,修改副本不会影响原始变量,这在可变结构体中极易导致难以排查的 Bug。
**错误示范 (可变结构体):**
struct MutablePoint { public int X; public int Y; }
// ...
MutablePoint p1 = new MutablePoint { X = 10 };
MutablePoint p2 = p1; // p2 是 p1 的副本
p2.X = 20;
// 此时 p1.X 仍然是 10,这往往不符合直觉
**正确示范 (不可变结构体):**
readonly struct ImmutablePoint
{
public int X { get; }
public int Y { get; }
public ImmutablePoint(int x, int y) { X = x; Y = y; }
}
// 使用 readonly 关键字可以强制编译器检查不可变性
===== 6. 总结 =====
* **Struct**: 为了性能(高频计算、海量小对象、减少 GC)。
* **Class**: 为了面向对象特性(继承、多态、大型业务对象)。
* **Enum**: 为了代码可读性和状态管理。