连锁推导法:在一个证明过程中,或一个比较复杂的推理过程中,将前一个推理的结论作为后一个推理的前提,一步接一步地推导,直到把需要的结论推出来。
我们在前面的知识中了解到值类型存储在堆栈(Stack)中,而引用类型存储在托管堆(Heap)中,堆栈的工作方式是以先进后出原则先保证先分配内存的变量后释放,你可以想像的出,堆栈中的变量是从后向前释放,这样就保证了堆栈中先进后出的规则不与变量的生命周期起冲突。
你可以仔细的想一下关于结构化编程的一些规则,C#对变量的声明要求是先定义后使用,变量的生命周期是从其定义开始直到程序的控制离开该变量所在的{}。以下代码描述了这个我们非常熟悉的事实
static void Main(string[] args)
{
int k = 10;//k的生命周期开始了
for (int i = 0; i <= 10 - 1; i++)//i的生命周期开始了
int m = k + i;//m的生命周期开始了
for (int j = i; j <= 10 - 1; j++)//j的生命周期开始了
int n = j * i;//n的生命周期开始了
//n的生命周期结束了
}//j的生命周期结束了
//m的生命周期结束了
}//i的生命周期结束了
//k的生命周期结束了
}
下面的图描述了这些变量的生命周期和堆栈的存储
<a href="http://blog.51cto.com/attachment/201203/195759131.gif" target="_blank"></a>
图
同时我们在C#的编程中可以发现一个非常有趣的现象,即我们不能将值类型设置为null,但可以对引用类型设置为null。
假设上述的代码改为如下形式
k = null; //k的生命周期结束了???
你考虑下第七行代码
这时候栈应该怎么处理呢?想像下,堆栈图会怎么样呢?显然堆栈到了第三步就不知道怎么样才可以把变量k销毁了。
<a href="http://blog.51cto.com/attachment/201203/195851736.gif" target="_blank"></a>
那为什么引用类型就可以设置为null呢?我们先看如下的代码
System.Collections.ArrayList k = new System.Collections.ArrayList();//k的生命周期开始了
k.Add(i);
int m = k.Count;//m的生命周期开始了
对应的堆栈中的处理大致如图
<a href="http://blog.51cto.com/attachment/201203/195907485.gif" target="_blank"></a>
我们可以看出变量k还是分配在堆栈中,但实际存放ArrayList实例的区域却是存储在堆中。对ArrayList的实例使用,是通过在堆栈中的变量k来间接的指向的。这样的好处是,将对象引用null和生命周期两个概念可以分离出来。
以下代码请仔细认知
<a href="http://blog.51cto.com/attachment/201203/195928105.gif" target="_blank"></a>
我们可以很清晰的观察到,k的生命周期并没有发生变化,只是当k = null;后,变量k不再指向堆中的有效地址了。
现在我们明白了,因为值类型变量直接在Stack中保存了数据,因此在生命周期结束前数据不能被任何形式的销毁,而引用类型变量在Heap中保存数据,所以赋值null其实是将对应在Heap中的数据销毁而不是结束变量的生命周期。
本文转自shyleoking 51CTO博客,原文链接:http://blog.51cto.com/shyleoking/803155