C# 结构和类的区别
2024 年 6 月 16 日 · 2 分钟读完
目录
项目里正好同时碰到结构体和类,把两者的差异和选用场景梳理一下。
结构体(struct)
结构体是值类型,可以包含字段、属性、方法、事件、运算符、索引器等成员。
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public void Display() => Console.WriteLine($"X: {X}, Y: {Y}");
}
特点:
- 值语义:赋值给另一个变量时复制的是整份数据,改其中一个不影响另一个。
- 不能继承:不能继承其他结构体或类(隐式继承自
System.ValueType),但可以实现接口。 - 小而高效:作为局部变量时通常分配在栈上,不需要 GC 回收,小型数据用结构体开销更小。
- 默认值:
default(Point)会把所有字段置为 0 / null。C# 10 之前不能自己写无参构造函数,C# 10 起可以。 - 必须完全初始化:自定义构造函数里要给所有字段赋值(C# 11 起编译器会自动把没赋值的字段补成默认值)。
类(class)
类是引用类型,是面向对象编程的基础,通过继承和多态实现复用和扩展。
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public void Display() => Console.WriteLine($"Name: {Name}, Age: {Age}");
}
特点:
- 引用语义:实例分配在托管堆上,变量里存的是引用。两个变量指向同一个对象时,改一个另一个也变。
- 继承和多态:单继承、多接口,基类变量可以指向子类对象。
- 构造函数:可以有多个;不写时编译器提供一个默认的无参构造函数。
- 由 GC 回收:没有引用以后,等垃圾回收器回收,见 什么对象会被 .NET 垃圾回收。
对比
| struct | class | |
|---|---|---|
| 类型 | 值类型 | 引用类型 |
| 赋值、传参 | 复制整份数据 | 复制引用 |
| 继承 | 只能实现接口 | 支持继承和多态 |
| 默认值 | 各字段为默认值 | null |
| 回收 | 随所在位置释放 | 由 GC 回收 |
| 适合 | 小、简单、频繁创建的数据 | 复杂、需要共享状态或继承的对象 |
选类:需要多个地方共享同一个实例;需要继承和多态;对象较大、生命周期较长。
选结构体:需要副本之间互不影响;数据小而简单(微软的建议是 16 字节以内);频繁创建和销毁,比如坐标点、颜色、一次 PLC 读数。
延伸
- 「值类型在栈上」不完全对:准确的说法是值类型存在它所在的地方。局部变量里的 struct 在栈上;但作为类的字段、数组元素,或者被装箱成
object时,它在堆上。 - 装箱的代价:把 struct 赋给
object或接口变量会装箱,在堆上复制一份。ArrayList、非泛型集合、string.Format("{0}", point)都会装箱,高频场景用泛型集合避免它。 - 可变结构体的坑:
list[0].X = 5;编译不过,因为list[0]返回的是副本;foreach里改 struct 也改不到原数据。所以结构体最好做成不可变的:C# 7.2 的readonly struct,或者 C# 10 的record struct。 - 传大结构体用
in:结构体较大又不想复制时,参数写成in Point p,按只读引用传递。 - Halcon 和 OpenCV 里的例子:
OpenCvSharp.Point、Rect、Scalar都是 struct,频繁创建也没有 GC 压力;而Mat、HObject是 class,因为它们持有大块的非托管图像内存,需要Dispose。
原文首发于 blog.csdn.net