C#中值类型和引用类型有什么区别?

来源:安卓APP网作者:缅甸程序员头衔:程序员
导读:本期聚焦于缅甸程序员创作的《C#中值类型和引用类型有什么区别?》,敬请观看详情。为什么同一个变量赋值后修改不影响原值,而另一个却跟着一起变?答案就藏在C#的类型系统里。本文从内存分配的角度讲解值类型与引用类型的本质差异,分析栈与堆各自的存储机制,结合代码示例演示赋值、传参、装箱拆箱等典型场景中的行为区别,并说明两者在性能、GC压力和默认值方面的表现差异,帮助你写出更可预测、更高效的C#代码。

C#的类型系统把所有类型分成两大阵营:值类型和引用类型。这个划分不是语法层面的装饰,而是直接决定了数据在内存中如何存储、赋值时是复制数据还是复制地址、参数传递时方法内部能否修改外部变量。理解这一点,是看懂C#程序行为的第一步。本文从内存原理、代码表现、装箱拆箱三个层面,把这两类类型的区别讲清楚。

C#中值类型和引用类型有什么区别?

一、内存分配:栈与堆的本质区别

值类型包括intdoubleboolcharstruct以及枚举等,它们的实例通常直接存储在栈上(或作为对象的一部分内联存储在堆中)。所谓直接存储,指的是变量本身就包含了完整的数据。比如声明一个int age = 25;,栈上这个位置存放的就是25这个数值本身,没有任何中间环节。

引用类型包括classstringarraydelegateinterface等。声明一个引用类型变量时,栈上存放的只是一个地址(引用),真正的对象数据在托管堆上。变量就像一张写着门牌号的纸条,顺着地址找过去,才能看到实际的房子。

这种存储差异带来一个直接后果:值类型变量的读写不需要间接寻址,访问速度快;引用类型每次访问都要先取出地址再跳转到堆上的实际位置。但要注意,值类型赋值时会整体复制数据,如果结构体很大,复制开销反而可能超过引用的间接开销,所以并非值类型永远更快。

二、赋值行为与代码演示

两类类型最直观的区别体现在赋值操作上。值类型赋值是深拷贝,两个变量从此互不相干;引用类型赋值只是复制了地址,两个变量指向同一个对象,改一个另一个也变。看下面这段代码:

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

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

class Program
{
    static void Main()
    {
        // 值类型赋值:复制完整数据
        PointStruct s1 = new PointStruct { X = 1, Y = 2 };
        PointStruct s2 = s1;
        s2.X = 100;
        Console.WriteLine(s1.X); // 输出 1,s1 不受影响

        // 引用类型赋值:只复制引用
        PointClass c1 = new PointClass { X = 1, Y = 2 };
        PointClass c2 = c1;
        c2.X = 100;
        Console.WriteLine(c1.X); // 输出 100,c1 和 c2 指向同一对象
    }
}

参数传递也遵循同样的逻辑。默认情况下(不加ref),方法收到的是变量的副本。对值类型参数的修改不会影响调用方;对引用类型参数的修改虽然会改变对象内容(因为副本里存的地址指向原对象),但如果在方法内把参数指向一个新对象,调用方依然感知不到。如果想真正按引用传递,需要显式使用refout关键字。

还有一个容易被忽略的细节:string虽然是引用类型,却表现出值类型的不可变行为。对字符串做拼接、替换等操作,返回的都是新对象,原字符串不会改变。这是.NET有意设计的,理解它能避免不少困惑。

三、装箱与拆箱:类型系统的交叉点

当值类型需要被当作引用类型对待时,比如把int放进ArrayList或赋值给object变量,就会发生装箱。装箱时CLR在堆上创建一个新对象,把值类型的值复制进去;拆箱则是反向操作,从堆对象中把值取回来。看示例:

int number = 42;
object boxed = number;      // 装箱:在堆上创建对象并复制值
int unboxed = (int)boxed;   // 拆箱:显式转换并复制回来

// 循环中反复装箱,性能损失明显
ArrayList list = new ArrayList();
for (int i = 0; i < 1000000; i++)
{
    list.Add(i); // 每次Add都会装箱一次
}

// 使用泛型集合避免装箱
List<int> genericList = new List<int>();
for (int i = 0; i < 1000000; i++)
{
    genericList.Add(i); // 不发生装箱
}

装箱的代价不小:堆分配带来内存压力,频繁装箱会制造大量小对象,增加垃圾回收器的工作量;拆箱还有类型检查开销,且如果拆箱的目标类型与装箱时不一致,会直接抛出InvalidCastException。这就是泛型集合List<T>全面取代ArrayList的原因,泛型让值类型在集合中保持原样存储,零装箱成本。

四、如何选择:struct还是class

实际开发中选值类型还是引用类型,可以参考几条经验法则。微软官方建议:类型实例较小(约16字节以下)、生命周期短、不可变、且经常按值复制时,用struct比较合适,典型的例子是DateTime、坐标点、颜色值。反之,类型体积大、需要被多方共享修改、作为继承体系的一部分,就应该用class

还要留意默认值的差异:值类型永远有默认值,int默认是0,结构体各字段取各自默认值,不会为null(可空值类型除外);引用类型默认是null,使用前必须检查,否则会撞上NullReferenceException。在可空引用类型特性开启后,编译器会帮助检查潜在的空引用问题,建议在新项目中启用。

总结一下:值类型存储数据本身,赋值即复制,分配在栈上或内联,不增加GC压力;引用类型存储地址,赋值共享对象,数据在托管堆上,由GC管理生命周期。掌握这条主线,再遇到变量行为诡异的问题时,从内存模型出发分析,基本都能找到答案。

C#值类型C#引用类型值类型与引用类型区别修改时间:2026-09-15 03:58:27

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