导读:本期聚焦于守望者创作的《C#中的struct和class怎么选 C#值类型与引用类型深度解析》,敬请观看详情。判断C#中该用struct还是class,本质上是在区分值类型与引用类型的行为差异。struct实例在栈上分配,若装箱或作为类字段则可能随对象进入堆,赋值时复制整个数据块;class实例始终在托管堆上分配,变量保存的是对象引用。这一差异直接影响拷贝语义、默认相等性、可空性以及垃圾回收压力。对于坐标、复数、颜色等小型不可变数据,struct能减少堆分配和GC暂停;而涉及继承、多态、大型可变状态、共享引用的场景,class更合适。本文从内存布局、赋值行为、默认构造函数、装箱拆箱、性能基准等角度深度解析两种类型的取舍,并给出明确的选择检查清单,帮助读者避免在结构体上误用可变字段、滥用只读属性等常见陷阱。

在C#里,struct和class都可以用来封装字段、属性与方法,但它们在内存分配、赋值语义、继承能力和性能特征上有着本质区别。选错类型通常不会立刻导致编译错误,却可能带来难以察觉的拷贝逻辑错误或额外的垃圾回收压力。要做出合理选择,需要先理解值类型与引用类型在运行时的行为差异。本文从内存分配、赋值与传参、面向对象能力以及性能开销几个维度展开,并给出可操作的选型检查清单。

C#中的struct和class怎么选 C#值类型与引用类型深度解析

一、内存分配与赋值语义:栈与堆的分野

struct是值类型,它的实例通常分配在当前线程栈上,但这不是绝对的:当struct作为类的字段、数组元素或装箱对象时,它的数据会随宿主对象一起存储在托管堆中。class是引用类型,实例本身始终分配在托管堆上,栈或寄存器中保存的只是指向堆内存的引用。理解这一点对性能分析有帮助:栈分配和释放速度快,不需要垃圾回收;堆分配则统一由GC管理。

赋值行为是两者最容易混淆的地方。值类型赋值会复制整个数据块:把struct变量A赋给B后,A和B拥有两份完全独立的数据。引用类型赋值只复制引用:把class变量C赋给D后,C和D指向同一个堆对象,通过任意一个引用修改字段,另一个引用都能观察到变化。方法传参默认也遵循同样的语义:struct参数会按值复制一份,方法内部修改形参不会影响调用方变量;class参数传递的是引用的副本,修改对象字段会影响原对象,但如果对形参重新赋值,调用方变量不会改变。

下面的示例直观展示了二者的赋值区别:

struct PointStruct
{
    public int X;
    public int Y;
}

class PointClass
{
    public int X;
    public int Y;
}

var s1 = new PointStruct { X = 1, Y = 2 };
var s2 = s1;
s2.X = 100;

var c1 = new PointClass { X = 1, Y = 2 };
var c2 = c1;
c2.X = 100;

// s1.X 仍然为 1,因为发生了完整复制
// c1.X 变为 100,因为 c1 和 c2 指向同一对象

还要注意默认值和可空性差异。值类型变量不能为null,除非使用可空值类型 Nullable<PointStruct>PointStruct?;引用类型变量默认可以为null。未显式初始化的struct字段会得到其默认值,例如int为0、bool为false,而class的字段在对象创建时也会被初始化为默认值,但class引用本身默认是null。

二、继承、多态与构造限制

class是面向对象设计的核心载体,支持单继承、接口实现、虚方法、抽象类以及protected访问修饰符。struct则被设计为轻量数据容器,隐式密封且不能继承其他类或struct,也不能作为基类被继承。struct只能实现接口,这限制了它在需要多态扩展场景中的使用。

构造函数方面,class可以自定义无参构造函数、带参构造函数、静态构造函数和析构函数;struct在较新的C#语言版本中已经放宽了一些限制,但整体上对无参构造函数、字段初始化器和析构函数仍有较多约束。传统上struct不能声明显式无参构造函数,实例字段也不能在声明时直接初始化。因此定义一个struct时,通常需要把初始化逻辑集中在带参构造函数或属性赋值中。

多态行为也明显不同。class可以通过虚方法和重写实现动态分派,例如基类引用指向子类对象时调用子类重写的方法。struct虽然可以重写 ToStringEqualsGetHashCode 等来自 object 的虚方法,但自身不能定义新的虚方法,也没有继承层次。默认相等性方面,class使用引用相等,只有两个引用指向同一对象时才相等;struct继承自 ValueType,默认使用反射逐字段比较,虽然语义上更接近值相等,但性能较差。因此只要把struct用作字典键或需要比较相等性,强烈建议重写 EqualsGetHashCode

下面是一个实现接口的struct示例,展示struct也能参与一定程度的抽象,但没有继承能力:

interface IMeasurable
{
    double GetArea();
}

struct Rectangle : IMeasurable
{
    public double Width;
    public double Height;

    public double GetArea()
    {
        return Width * Height;
    }
}

IMeasurable shape = new Rectangle { Width = 3, Height = 4 };
double area = shape.GetArea(); // 这里会发生装箱,因为接口类型是引用类型

代码中 shape 变量类型是接口 IMeasurable,把struct赋值给接口变量时发生装箱,这是性能分析中需要特别留意的点。如果只是局部调用 GetArea,直接使用 Rectangle 类型可以避免装箱。

三、性能影响:装箱、复制成本与垃圾回收压力

选择struct还是class,性能往往是决定性因素之一。struct在局部变量场景下分配在栈上,栈的分配只是移动栈指针,几乎零成本,方法返回后自动清理,不产生垃圾回收压力。class对象在堆上分配,当对象不再被引用时需要GC介入回收。如果程序高频创建大量小对象,例如在循环中生成坐标点、向量、颜色值,使用class可能导致频繁的第0代垃圾回收,影响吞吐和延迟。

但struct也并非没有代价。值类型赋值和传参会复制整个结构体,如果struct体积很大,拷贝成本可能超过堆分配和GC开销。官方设计指南建议将struct保持为不可变且大小不超过16字节。超过这个规模时,应更倾向于使用class,或者使用 readonly structref 参数传递来减少复制。

装箱是另一个隐藏开销。每当把一个struct赋值给 object、接口或传递到需要 object 的方法时,都会生成一个堆上的装箱副本。频繁装箱不仅产生垃圾,还会造成额外的内存拷贝。泛型可以有效避免装箱,例如 List<PointStruct> 在存储和遍历时不会对元素装箱,而 ArrayList 之类的非泛型集合则会。因此在现代C#中,优先使用泛型集合存放struct。

下面的代码展示了大量小对象场景下,struct和class在数组遍历时的性能差异:

struct Vector3
{
    public float X;
    public float Y;
    public float Z;
}

class Vector3Class
{
    public float X;
    public float Y;
    public float Z;
}

var structArray = new Vector3[100000];
for (int i = 0; i < structArray.Length; i++)
{
    structArray[i].X = i;
}

var classArray = new Vector3Class[100000];
for (int i = 0; i < classArray.Length; i++)
{
    classArray[i] = new Vector3Class();
    classArray[i].X = i;
}

上面的 for 循环中,structArray 的元素是内联在数组中的连续内存,赋值时不需要额外的堆分配;而 classArray 的每个元素需要单独 new 一个对象,数组只保存引用,堆上会多出10万个独立对象。这个差异在高频更新场景中会非常明显。

四、选型检查清单与常见误区

判断逻辑上是否具有值语义是第一步。如果两个对象在数据完全相同的情况下应当被视为相等,例如坐标、复数、颜色、日期范围,那么struct更符合直觉。如果对象代表一个独立的实体,有自己的生命周期和身份,即使所有字段都相同也不应视为同一个对象,例如用户、订单、数据库连接,那么class更合适。

可以从下面几个问题入手:是否需要继承或多态?是则用class;对象是否小于16字节且不可变?是则优先struct;是否需要在方法间共享同一份状态?是则用class;是否频繁在泛型集合中存储与遍历?是则struct可能更高效;是否经常赋值给object或接口?是则struct可能带来装箱开销,需要重新评估。

C# structC# class值类型与引用类型修改时间:2026-08-27 10:04:40

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。