更正:值类型并不总是分配在栈上

问:.NET中值类型和引用类型的区别是什么?

答:值类型分配在栈上,引用类型分配在堆上。

 

这样的问答我见过无数无数,很惭愧,我也曾经说过这样的话,实在抱歉,特此更正。

 

我不是学院派的,但是既然要写点东西出来,查点资料还是必须的,所以我又翻开了宝典——“当当当当!”——《CLR Via C#》,书中是这么描述的:

引用类型总是从托管堆上分配的,叽里呱啦叽里呱啦……

使用引用类型时,必须注意到可能存在的一些性能问题,叽里呱啦叽里呱啦……

为了提升简单的常用类型的性能,CLR提供了名为“值类型”的轻量级类型。值类型的实例通常是在一个线程的堆栈上分配的,叽里呱啦叽里呱啦……

注意到了吗,谁也没说值类型就一定在栈上分配啊,人家只是说通常。而且这部分内容应该是Jeffrey直接从《.NET Framework Programming》一书中直接拷贝(继承)过来的,Ctrl+C & Ctrl+V,你懂的。

但是由于撰文的逻辑,和段落的安排,这一处的描述很容易让人理解为:

引用类型一定是在堆上的,没错,引用类型会存在性能问题,也没错,所以出现了传说中的值类型,嗯嗯,所以值类型是分配在…………哈?

我当然不是在说Jeffrey蓄意误导读者,只是太多人读了一半就以为然了而已。

 

那么好吧,回归原点。“值”类型,对,是“值”类型,没人叫他“栈”类型吧。

这也就说明了值类型应该被关注的特征是它是copy by value的,而不是它具体是分配在什么地方的。然而在目前,显然,大多数人提到值类型,第一个想到的是它是分配在栈上,而不是它的copy by value特性,这样也引发了很多很多的bug!!

好吧,说了这么多,举一些实例吧。

1. 引用类型里面的成员变量,即使是值类型的,也会和引用类型的实例一起被分配到堆上。

2. 值类型的数组。数组是引用类型的,所以里面的值也都会在堆上。

哦?你好像明白了?作为引用类型内部的成员的值类型是会随着引用类型被分配到堆上的。

所以,只有所有的本地值类型的局部变量会被分配到栈上!?

 

呃,先看下面这段code:

/// <summary>
/// Interaction logic for Window2.xaml
/// </summary>
public partial class Window2 : Window
{
    public Window2()
    {
        InitializeComponent();
        Foo();
    }

    private void Foo()
    {
        int number = 123;
        button.Click += (s, e) => MessageBox.Show(number.ToString());
    }
}

14行的number变量是值类型,也是局部变量,但是它在栈上吗?显然不在,不然button click的时候它怎么被显示出来。

其实.NET在处理闭包的时候,会生成一个辅助类,并把闭包内的变量设置成为这个辅助类的成员变量。所以根本上,这个情况跟第一点还是一样的,只是更加隐蔽而已。(匿名委托和lambda表达式的出现导致了这个杯具,@Jeffrey)

3. 闭包内的局部值类型变量也会被分配到堆上。

所以今后一定要严记,值类型和引用类型的区别,不是分配到哪里的关系,而是跟他们的名字一样,一个是copy by value,一个是copy by reference。

*无责任备注:面试的时候如果被问到这个,最好多做些解释,而不要直接说结论,因为你的面试官可能也认为它们的区别只是分配到栈上和堆上,因为这个丢了工作就不好了。

 

 

更多的细节可以参考扩展阅读:

The Stack Is An Implementation Detail, Part One

The Stack Is An Implementation Detail, Part Two

posted @ 2010-02-04 14:56  redjackwong  阅读(1111)  评论(17编辑  收藏  举报