最近博客园上连续出现了几篇关于堆VS栈 值类型VS引用类型的文章。
一个是关于C# 堆VS栈的,深入浅出,动图清晰明了,链接如下
二是从Eric Lippert(Eric Lippert is a principal developer on the C# compiler team)的文章演变出的两篇,链接如下
下面是我的思考和总结,希望能与大家分享和讨论。
最近和同事一起面试时也经常问这个问题,被面的同学很多回答为“值类型存储在栈里,引用类型存储在堆里”,首先我们先不去深究这个说法是否准确(上面的文章里已经说的很清楚)。
值类型和引用类型的区别是存储位置的不同吗?
Eric Lippert给出的答案----值类型和引用类型的区别在语义层面,与存储位置无关。存储位置是C#编译运行时的分配,是实现细节。
或许C# compiler的未来版本中,值类型也可能不存储在栈里;
或者某一个系统平台中并不存在堆和栈的概念。
结论
“值类型和引用类型的区别,与存储位置无关”。
Eric Lippert说 - 值类型和引用类型的区别在语义层面。要怎么理解?我们思考的视角应该是C#语言语义和使用上。
结论
“值类型传递的是值(不同的实例,互不影响),引用类型传递的是引用(同一个实例,互相影响)”
如同一句废话!那我们试着换几种方式来描述(可能不准确)
1. 值类型是私有的,是持有者自己的东西;引用类型是共享的,大家共有的东西。
2. 值类型是多实例的,每次传递都创建新实例;引用类型是单实例的,每次传递都是同一个实例,如设计模式中的单例。
3. 值类型的生命周期和持有者相同;引用类型的生命周期和持有者不同。(有关生命周期的言论)
如何实现值类型和引用类型的存储?
有两种存储方式可供选择
一 直接存储的优点是性能高,缺点是共享不方便。(栈或者堆上)
二 间接存储的优点是共享方便,缺点是多了一次跳转,有性能损失。(堆上)
我们要的关注哪些问题?性能,共享方式(生命周期)
值类型是不共享的,它的生命周期和持有者相同,所以可以直接存储,如果间接存储,会多一次跳转,没有意义的性能损失。
引用类型是共享的,它的生命周期和持有者不同,所以采用间接存储,如果直接存储,是很难实现共享和生命周期的不同。
结论
值类型是直接存储,引用类型是间接存储。是基于实现的考量,不是值类型和引用类型的区别。
生命周期是从实现角度思考的推论,也不是值类型和引用类型的区别。
思考后发现,所有值类型都可以被引用类型所替代,那为什么要有值类型呢?没有得出理想的答案,推测有两种可能
一 历史传承。
二 基于性能的考量。这个应该是实现级别的事情,为什么被暴露在语言级别上?没有办法解决吗?(对于值来说,引用类型多了一次跳转;对于引用来说,值的传递多了一次深克隆)
在工作中很少(几乎没有)使用struct,因为性能上的收益远远无法弥补维护成本的损失。
(不能要求每个开发人员都很了解struct和class的不同,并在修改代码时意识到使用的是struct而不是class)
值和引用类型的区别是语言和使用级别的
值类型传递的是值(不同的实例,互不影响),引用类型传递的是引用(同一个实例,互相影响)
有关值类型和引用类型储存在栈或者堆上的言论,是基于实现细节的思考,是当前的实现方式,不是值类型和引用类型的区别。
有关值类型和引用类型生命周期的言论,是基于实现细节的思考,是当前的实现方式,不是值类型和引用类型的区别。
值类型(struct)更多是性能上的收益,C#中定义的基础值类型已经足够,开发中尽量避免定义值类型(struct)。