C# 值类型与引用类型 详解
C# 值类型与引用类型 完整详解一、核心本质区别内存存储1. 内存分配位置值类型Value Type变量数据直接存储在栈(Stack)上变量本身就是数据。引用类型Reference Type实际数据存放在堆(Heap)栈上只存一个内存地址引用通过地址指向堆中的对象。2. 赋值行为差异值类型赋值完整拷贝副本赋值时把全部数据复制一份两个变量完全独立修改其中一个不会影响另一个。引用类型赋值拷贝地址浅拷贝只复制堆地址两个变量指向同一个堆对象任意一个修改对象内容两边同时变化。3. 生命周期与回收值类型栈自动回收超出作用域立刻销毁无GC开销。引用类型靠CLR垃圾回收器(GC)管理堆内存无任何引用指向对象后才会被GC回收。4. 默认值值类型必有默认值不能为null可空值类型除外引用类型默认值是null代表栈上没有指向任何堆对象二、值类型完整分类所有值类型隐式继承System.ValueType而ValueType本身又继承object。1. 简单内置值类型分类类型说明整数sbyte、byte、short、ushort、int、uint、long、ulong固定长度数字浮点float、double小数高精度小数decimal财务计算专用布尔booltrue/false字符char单个Unicode字符2. 枚举enum底层基于整型属于值类型enumColor{Red,Green}// 值类型Colorc1Color.Red;Colorc2c1;// 拷贝独立副本c1Color.Green;// c2不受影响3. 结构体struct自定义值类型可包含字段、方法、属性、构造函数structPoint// 值类型{publicintX;publicintY;}Pointp1newPoint{X1,Y2};Pointp2p1;// 完整复制X、Yp1.X100;// p2.X 仍然是 1互不干扰注意C# 10 支持无参构造函数结构体默认无参构造永远存在自动赋0/默认值。4. 可空值类型NullableT/T?普通值类型不能为null包装后允许空int?numnull;// Nullableintif(num.HasValue){}值类型内存图解inta10;intba;a20;栈内存栈a 10 → 修改后20 栈b 10 独立副本不受影响三、引用类型完整分类所有引用类型直接/间接继承System.Object数据存堆栈存引用地址。1. 类class最常用自定义引用类型实例分配在堆classPerson// 引用类型{publicstringName;}Personp1newPerson{Name张三};Personp2p1;// 仅复制堆地址指向同一个对象p1.Name李四;Console.WriteLine(p2.Name);// 输出李四同步修改内存图解栈p1 → 0x001堆地址 栈p2 → 0x001 堆0x001{ Name张三 } → 修改为李四2. 字符串string特殊引用类型不可变immutable属于class存在堆一旦创建无法修改拼接/替换会生成全新字符串字符串池优化相同字面量复用地堆内存。strings1abc;strings2s1;s1xyz;// 新建堆对象s2仍指向abc3. 数组Array不管元素是值类型还是引用类型数组本身永远是引用类型int[]arr1newint[2]{1,2};int[]arr2arr1;arr1[0]99;Console.WriteLine(arr2[0]);// 99共享数组4. 接口interface本身不能实例化但接口变量是引用类型存储实现类对象的地址。5. 委托delegate、事件event本质封装方法指针属于引用类型。6. 动态类型dynamic底层基于object引用类型。四、装箱与拆箱值类型 ↔ object1. 装箱(Boxing)值类型 → 引用类型把栈上的值类型数据复制到堆中包装为object生成引用intnum10;// 栈objectobjnum;// 装箱堆创建object副本obj存堆地址开销分配堆内存、拷贝数据频繁装箱影响性能。2. 拆箱(Unboxing)object → 值类型从堆的object中取出原始值类型数据复制回栈必须强制转换intnum2(int)obj;// 拆箱错误示范类型不匹配会抛InvalidCastException。避免装箱优化使用泛型ListT而非ArrayList泛型容器不会装箱拆箱。五、关键易混淆知识点1. struct vs class 核心选用场景用 struct值类型满足全部小型数据通常实例大小16字节数据轻量很少做赋值拷贝无需继承、多态语义是单一数据点坐标、颜色、尺寸用 class引用类型满足任意数据量大需要频繁传递、共享对象需要继承、多态、接口多实现语义是业务实体用户、订单、商品2. ref / out / in 参数改变值类型传递逻辑默认值类型传参是值拷贝加ref后传递栈变量地址方法内修改会影响外部变量staticvoidModify(refintx){x999;}inta10;Modify(refa);// a 9993. 只读结构体readonly struct结构体所有字段只读拷贝时编译器可做优化减少复制开销。4. 字符串特殊的相等判断string重载比较字符内容object.ReferenceEquals(s1,s2)比较是否指向同一个堆地址判断字符串池复用5. 空值区别值类型int不能nullint?才允许null引用类型string默认null代表无堆对象六、对比总结表对比维度值类型(Value Type)引用类型(Reference Type)存储位置栈Stack数据堆Heap栈存地址赋值逻辑完整复制数据副本仅复制内存地址共享对象默认值数字0、false、\0不可nullnull无堆对象内存回收栈自动释放无GCGC标记清除回收堆内存继承根System.ValueType → object直接继承object代表类型struct、enum、int/bool/char等class、string、数组、委托、接口修改传递副本互不干扰一处修改全部同步装箱拆箱支持有性能损耗无需装箱七、完整演示代码usingSystem;// 值类型结构体structPoint{publicintX;publicintY;}// 引用类型类classStudent{publicstringName;}classProgram{staticvoidMain(){// 值类型演示 Pointp1newPoint{X10,Y20};Pointp2p1;p1.X999;Console.WriteLine($值类型 p2.X {p2.X});// 10不受影响// 引用类型演示 Students1newStudent{Name小明};Students2s1;s1.Name小红;Console.WriteLine($引用类型 s2.Name {s2.Name});// 小红同步变化// 装箱拆箱 intnum100;objectboxObjnum;// 装箱intunboxNum(int)boxObj;// 拆箱Console.WriteLine($拆箱结果{unboxNum});}}输出值类型 p2.X 10 引用类型 s2.Name 小红 拆箱结果100八、常见踩坑点结构体作为List元素修改无效ListPoint取出的是结构体副本直接修改属性不会改变集合内数据要用索引重新赋值。频繁new class产生大量GC高频循环内创建类实例会造成堆碎片可改用结构体或对象池优化。字符串拼接性能差string不可变大量拼接用StringBuilder。拆箱强制转换类型错误抛异常装箱是什么类型拆箱必须对应类型不能隐式转换。数组永远是引用类型哪怕数组元素是int值类型数组本身传递依然共享。