# 深入解析 C# 中类(class)与结构(struct)的异同

在 C# 这门以类型系统著称的面向对象语言中,class(类) 与 struct(结构) 是两种最核心、也最容易被混淆的类型定义方式。

它们都可以封装数据与行为,但在内存模型、语义设计、继承能力、性能特征等方面存在本质差异。理解这些差异,不仅关系到代码正确性,更直接影响系统性能与可维护性。


一、共同点:class 与 struct 能做什么是一样的?

尽管底层语义不同,class 与 struct 在语言层能力上高度一致:

  • 封装能力

    • 都可以定义字段(Field)、属性(Property)、方法(Method)、索引器(Indexer)、事件(Event)
    • 均支持运算符重载
  • 接口实现

    • 都可以实现一个或多个接口(Interface)
  • 访问控制

    • 支持 public / private / protected / internal
  • 泛型支持

    • 都可作为泛型参数
    • 都可定义为泛型类型(class<T> / struct<T>
  • 构造函数

    • 都支持参数化构造函数(struct 有额外约束)

👉 结论

从“能写什么代码”来看,class 与 struct 非常相似;
真正的差异,来自于它们的类型语义与内存模型


二、本质差异:引用类型 vs 值类型

维度classstruct
类型本质引用类型(Reference Type)值类型(Value Type)
默认内存位置托管堆(Heap)取决于宿主(通常为栈)
继承能力支持单继承不支持继承(隐式继承 ValueType)
构造函数可自定义无参 / 有参不可自定义无参,必须初始化全部字段
析构函数支持(由 GC 调用)不支持
赋值语义引用拷贝值拷贝
可空性默认可为 null默认不可为 null
GC 影响需要 GC 回收栈上无 GC,堆上随宿主回收
装箱拆箱不存在转 object / interface 会发生

三、几个最容易踩坑的关键差异

1️⃣ 值拷贝 vs 引用拷贝(核心中的核心)

Animal a1 = new Animal("Dog");
Animal a2 = a1;
a2.Species = "Husky";
Console.WriteLine(a1.Species); // Husky
  • class:变量中存的是引用地址
  • 多个变量 → 同一个对象
Cat c1 = new Cat("橘猫");
Cat c2 = c1;
c2.Name = "布偶猫";
Console.WriteLine(c1.Name); // 橘猫
  • struct:变量中存的是完整数据副本
  • 修改副本不会影响原值

⚠️ 注意:值拷贝 ≠ 逻辑深拷贝

  • 如果 struct 内部包含引用类型字段,只会拷贝引用本身

2️⃣ struct 的内存位置并不“一定在栈上”

struct 的存储位置,取决于它的宿主
  • 局部变量 → 通常在栈
  • class 字段 → 在堆
  • 数组元素 → 在堆
  • async / iterator → 可能被提升到堆

👉 不要用“struct 在栈上 / class 在堆上”作为绝对判断


3️⃣ struct 的构造函数限制

struct Point
{
    public int X;
    public int Y;

    public Point(int x, int y)
    {
        X = x;
        Y = y; // 必须初始化所有字段
    }
}
  • ❌ 不允许自定义无参构造函数
  • ✅ 参数化构造函数必须初始化全部字段
  • 编译器始终保留一个“零值初始化路径”

四、代码对比示例(精简直观)

// struct:值语义
struct Cat
{
    public string Name;
    public Cat(string name) => Name = name;
}

// class:引用语义
class Animal
{
    public string Species;
    public Animal(string species) => Species = species;
}
Cat c1 = new Cat("A");
Cat c2 = c1;
c2.Name = "B";   // c1 不受影响

Animal a1 = new Animal("Dog");
Animal a2 = a1;
a2.Species = "Cat"; // a1 被影响

五、如何选择?实战决策指南

✅ 优先选择 struct 的场景

  • 小而简单的数据结构(2~4 个字段)
  • 强调值语义(数学、坐标、金额)
  • 高频创建、短生命周期
  • 不可变对象(readonly struct)

典型示例:

  • int / DateTime / Guid
  • Point / Vector / Color

✅ 优先选择 class 的场景

  • 复杂业务对象
  • 需要继承 / 多态
  • 生命周期较长
  • 需要 null 表示“不存在”

典型示例:

  • User / Order
  • Service / Controller
  • 领域模型(Domain Model)

六、面试高频问题(附标准回答)

下面问题均来自 C# / .NET 中高级面试真实高频题,可直接背诵使用。


Q1:class 和 struct 的本质区别是什么?

标准回答:

class 是引用类型,struct 是值类型。
class 变量保存的是对象引用,多个变量可指向同一实例;
struct 变量保存的是实际数据,赋值会产生值拷贝。


Q2:struct 一定分配在栈上吗?

标准回答:

不一定。struct 的存储位置取决于它的宿主。
作为局部变量时通常在栈上,作为类字段或数组元素时分配在堆上。


Q3:为什么 struct 不支持继承?

标准回答:

struct 的设计目标是轻量级值语义,如果允许继承会引入对象身份、多态和额外间接层,破坏其性能和语义一致性。


Q4:什么时候不应该使用 struct?

标准回答:

当对象体积较大、需要继承、多态,或生命周期较长时,不应使用 struct,否则会导致频繁值拷贝和性能下降。


Q5:struct 作为方法参数时会发生什么?

标准回答:

默认情况下会发生值拷贝,方法内部修改不会影响原变量。
可通过 ref / in / out 传递以避免拷贝。


Q6:什么是装箱和拆箱?什么时候发生?

标准回答:

当 struct 被转换为 object 或接口类型时会发生装箱(值复制到堆)。
从 object 再转回 struct 时发生拆箱。
装箱拆箱会产生额外分配和性能开销。


Q7:为什么 struct 不能定义无参构造函数?

标准回答:

因为 CLR 必须保证所有 struct 都可以被零值初始化,允许自定义无参构造函数会破坏这一约定。


Q8:readonly struct 有什么作用?

标准回答:

readonly struct 保证实例不可变,可避免 defensive copy,提高性能和线程安全性。


Q9:record struct 和 record class 有什么区别?

标准回答:

record struct 是值语义,record class 是引用语义。
二者都提供值相等性、解构和 with 表达式,但内存与拷贝语义不同。


Q10:实际项目中如何选择 class 还是 struct?

标准回答:

判断标准不是性能,而是语义:

  • 需要对象身份、继承、多态 → class
  • 表示纯数据、值语义、短生命周期 → struct

七、一句话总结(面试 & 记忆版)

class = 引用语义 + 继承 + 复杂对象
struct = 值语义 + 轻量数据 + 性能友好

终极判断标准:

👉 你关心的是“对象身份”,还是“数据本身”?

class = 引用语义 + 继承 + 复杂对象
struct = 值语义 + 轻量数据 + 性能友好

选择标准不是“性能迷信”,而是:

👉 你要的是“对象身份”,还是“数据本身”?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcome_com

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值