C# 中 Class 与 Struct 的核心差异:引用类型与值类型对比
在 C# 开发中,合理选择 class 与 struct 对程序性能至关重要。class 是引用类型,存储在堆中,传递的是对象引用;struct 是值类型,通常存储在栈中,传递时进行数据拷贝。本文将详细对比两者在内存管理、默认值、继承规则及构造函数方面的差异,并通过实际代码演示其行为表现。
一、核心类型与内存存储
理解 class 和 struct 的首要任务是掌握它们的内存模型:
- Class(引用类型):实例分配在堆内存中,变量本身存储的是指向堆中对象的引用(指针)。当使用
=赋值给另一个变量时,仅复制引用地址,两个变量指向同一对象。 - Struct(值类型):实例通常直接包含在栈内存中(或在引用类型的字段内作为内联数据)。变量包含完整的数据。赋值操作会将所有字段值复制到新位置,彼此独立。
二、Class 的特性细节
第1步:了解类的引用行为
由于类是引用类型,多个引用可以指向同一个内存地址。修改其中一个引用的属性,会影响所有指向该对象的引用。
XClass xClass = null; // 语法正确,类可以为null
XClass xClass3 = new XClass() { Id = 7, Name = "天晴之海" };
XClass xClass4 = xClass3; // 此时xClass4与xClass3指向同一内存地址
// ReferenceEquals(xClass3, xClass4) 将返回 true
第2步:构造函数规则
C# 中的类具有灵活的构造函数定义:
- 默认情况:如果未显式定义任何构造函数,编译器会自动提供一个无参构造函数。
- 自定义情况:一旦定义了带参数的构造函数,且未手动添加无参构造函数,系统将不再提供默认的无参构造函数,必须通过参数初始化或显式补充无参构造。
第3步:字符串的特殊性
虽然 string 在底层是 class(引用类型),但其具有不可变性。对字符串的任何修改(如拼接)都会创建新的对象实例,而非修改原内存中的数据。因此在逻辑上常被视为按“值”处理。
三、Struct 的特性细节
第1步:值类型的不可空性与默认值
Struct 是值类型,不能直接赋值为 null。其默认值由字段类型的默认值决定(例如 int 为 0,bool 为 false)。
XStruct xStruct = null; // 编译错误!
// 如需使用null,需借助可空类型
Nullable<XStruct> nullableStruct = null;
// 或简化写法
XStruct? nullableStruct = null;
第2步:继承与基类约束
所有 struct 都隐式继承自 System.ValueType(而 ValueType 又继承自 Object)。因此:
- 不支持继承其他类或结构。
- 不允许显式定义无参构造函数。系统始终提供隐藏的无参构造函数用于初始化默认值。
- 无法在声明处直接赋予字段初始值(如
int Id = 0;在结构中是非法的)。
第3步:相等性判断
由于 struct 重写了 Equals 方法,基于值的比较判定两个 struct 实例是否相等。若所有字段值一致,则视为相等,即便它们是不同的内存实例。
四、综合测试与现象观察
为了验证上述理论,我们构建了一个控制台应用程序 GenericTemplateDemo,分别对类和结构的赋值、比较进行测试。
第4步:代码实现
using System;
namespace GenericTemplateDemo
{
class Program
{
static void Main(string[] args)
{
// --- 结构 (Struct) 测试 ---
Console.WriteLine($"结构的默认值为类型名: {default(XStruct)}, 类型名为【{typeof(XStruct)}】");
// 1. 不同实例但值相同
XStruct xStruct1 = new XStruct("ABC") { Id = 5 };
XStruct xStruct2 = new XStruct("ABC") { Id = 5 };
Console.WriteLine($"[结构] 值比较 Equals: {xStruct1.Equals(xStruct2)}"); // True
Console.WriteLine($"[结构] 引用比较 ReferenceEquals: {ReferenceEquals(xStruct1, xStruct2)}"); // False
// 2. 赋值测试
XStruct xStruct3 = new XStruct("修吾") { Id = 8 };
XStruct xStruct4 = xStruct3; // 值拷贝
Console.WriteLine($"[结构] 赋值后引用比较: {ReferenceEquals(xStruct3, xStruct4)}"); // False
// --- 类 (Class) 测试 ---
Console.WriteLine($"\n类的默认值为null: {default(XClass) == null}");
// 1. 不同实例但值相同
XClass xClass1 = new XClass("毒瘴泉") { Id = 6 };
XClass xClass2 = new XClass("毒瘴泉") { Id = 6 };
Console.WriteLine($"[类] 值比较 Equals: {xClass1.Equals(xClass2)}"); // False (默认Object.Equals为引用比较)
Console.WriteLine($"[类] 引用比较 ReferenceEquals: {ReferenceEquals(xClass1, xClass2)}"); // False
// 2. 赋值测试
XClass xClass3 = new XClass("天晴之海") { Id = 7 };
XClass xClass4 = xClass3; // 引用拷贝
Console.WriteLine($"[类] 赋值后引用比较: {ReferenceEquals(xClass3, xClass4)}"); // True
}
}
/// <summary>
/// 测试用的结构
/// </summary>
public struct XStruct
{
// 自定义有参构造函数
public XStruct(string name)
{
Id = 0;
Name = name;
}
// 注意:结构体字段不能在声明处赋初值
public int Id;
public string Name;
}
/// <summary>
/// 测试用的类
/// </summary>
public class XClass
{
public XClass()
{
// 空实现
}
public XClass(string name)
{
Id = 0;
Name = name;
}
// 自动属性允许赋初值
public int Id { get; set; }
public string Name { get; set; }
}
}
第5步:运行结果图示
程序运行输出如下所示,清晰展示了结构与类在不同场景下的内存行为:
五、选择建议
在实际开发中选择数据类型时,可以参考以下原则:
- 轻量级数据结构:如果数据量小且生命周期短(如坐标点 Point、颜色 Color),优先考虑
struct,减少 GC(垃圾回收)压力并提升栈访问速度。 - 复杂对象与多态:如果数据量大、需要继承体系、或者需要传递引用语义(一个地方修改影响全局),应使用
class。 - 不可变数据:对于无需修改的常量配置数据,值类型往往更安全且高效。
总结
以上就是C#中class与struct区别与使用的详细内容,更多关于C#高级编程与内存管理的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









