在 C# 中,结构体(struct) 和 类(class) 是两种常用的类型,分别适用于不同的场景。以下是它们的主要区别:
1. 存储方式
- 结构体(struct):
- 是值类型,存储在**栈(stack)**中。
- 每次赋值或传递时会创建数据的副本。
- 类(class):
- 是引用类型,存储在**堆(heap)**中。
- 变量保存的是对象的引用,而非实际数据。
2. 性能
- 结构体(struct):
- 在小型、短生命周期的数据结构中性能较好,减少了内存分配和垃圾回收的开销。
- 由于复制时会生成数据副本,数据量大时可能带来性能开销。
- 类(class):
- 适用于需要动态分配内存或复杂数据结构的场景。
- 引用传递避免了数据复制的成本,但需要垃圾回收来释放内存。
3. 可空性
- 结构体(struct):
- 默认不能为 null。
- 可以通过 Nullable
或 T? 来使结构体支持空值。 - 类(class):
- 可以直接为 null,需要额外的空值检查。
4. 继承
- 结构体(struct):
- 不支持继承(即不能被另一个结构体继承,也不能继承其他类或结构体)。
- 隐式继承自 System.ValueType,这是所有值类型的基类。
- 类(class):
- 支持继承和多态。
- 可以实现复杂的对象层次结构。
5. 构造函数
- 结构体(struct):
- 默认提供无参构造函数,无法显式定义无参构造函数。
- 可以定义带参数的构造函数,但所有字段必须被显式初始化。
- 类(class):
- 必须显式定义构造函数来初始化对象。
- 支持无参、带参构造函数和析构函数。
6. 默认值
- 结构体(struct):
- 自动初始化为其字段的默认值(例如 0、false 或 null)。
- 类(class):
- 未初始化的引用类型变量默认值为 null。
7. 可变性
- 结构体(struct):
- 一般用于表示不可变对象,推荐将其字段定义为只读。
- 如果需要可变性,操作时会复制整个结构体。
- 类(class):
- 通常表示可变对象,字段和属性的修改会影响所有引用该对象的变量。
8. 内存分配和垃圾回收
- 结构体(struct):
- 在栈上分配内存,生命周期短。
- 不需要垃圾回收。
- 类(class):
- 在堆上分配内存,由垃圾回收器管理内存的释放。
9. 多线程场景
- 结构体(struct):
- 由于是值类型,每个线程持有独立的副本,因此线程安全性较高。
- 类(class):
- 由于是引用类型,多线程操作可能需要额外的同步机制。
10. 适用场景
- 结构体(struct):
- 用于表示简单的、逻辑上不可变的小数据对象。
- 示例:Point, TimeSpan, DateTime 等。
- 类(class):
- 用于表示复杂的、可变的数据结构。
- 示例:List, Dictionary, HttpClient 等。
代码示例
结构体
public struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
Point p1 = new Point(1, 2);
Point p2 = p1; // 复制数据
p2.X = 3; // 编译错误,X 是只读
类
public class Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
Point p1 = new Point(1, 2);
Point p2 = p1; // 引用复制
p2.X = 3; // 修改影响所有引用
总结
特性 | 结构体(struct) | 类(class) |
类型 | 值类型 | 引用类型 |
存储位置 | 栈 | 堆 |
继承支持 | 不支持继承 | 支持继承和多态 |
构造函数 | 无法定义无参构造函数 | 支持自定义构造函数和析构函数 |
性能 | 较高,适用于小型数据 | 较低,适用于复杂数据结构 |
可变性 | 通常不可变 | 通常可变 |
内存管理 | 无需垃圾回收 | 需要垃圾回收 |
选择指南:
- 使用 结构体 时,优先考虑小型、不可变数据。
- 使用 类 时,适用于复杂、可变对象或需要继承的场景。