连锁推导法:在一个证明过程中,或一个比较复杂的推理过程中,将前一个推理的结论作为后一个推理的前提,一步接一步地推导,直到把需要的结论推出来。

我们在前面的知识中了解到值类型存储在堆栈(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的生命周期结束了

}

下面的图描述了这些变量的生命周期和堆栈的存储

同时我们在C#的编程中可以发现一个非常有趣的现象,即我们不能将值类型设置为null,但可以对引用类型设置为null。

假设上述的代码改为如下形式

static void Main(string[] args)

{

int k = 10;//k的生命周期开始了

for (int i = 0; i <= 10 - 1; i++)//i的生命周期开始了

{

int m = k + i;//m的生命周期开始了

k = null; //k的生命周期结束了???

for (int j = i; j <= 10 - 1; j++)//j的生命周期开始了

{

int n = j * i;//n的生命周期开始了

//n的生命周期结束了

}//j的生命周期结束了

//m的生命周期结束了

}//i的生命周期结束了

//k的生命周期结束了

}

你考虑下第七行代码

k = null; //k的生命周期结束了???

这时候栈应该怎么处理呢?想像下,堆栈图会怎么样呢?显然堆栈到了第三步就不知道怎么样才可以把变量k销毁了。

那为什么引用类型就可以设置为null呢?我们先看如下的代码

static void Main(string[] args)

{

System.Collections.ArrayList k = new System.Collections.ArrayList();//k的生命周期开始了

for (int i = 0; i <= 10 - 1; i++)//i的生命周期开始了

{

k.Add(i);

int m = k.Count;//m的生命周期开始了

for (int j = i; j <= 10 - 1; j++)//j的生命周期开始了

{

int n = j * i;//n的生命周期开始了

//n的生命周期结束了

}//j的生命周期结束了

//m的生命周期结束了

}//i的生命周期结束了

//k的生命周期结束了

}

对应的堆栈中的处理大致如图

我们可以看出变量k还是分配在堆栈中,但实际存放ArrayList实例的区域却是存储在堆中。对ArrayList的实例使用,是通过在堆栈中的变量k来间接的指向的。这样的好处是,将对象引用null和生命周期两个概念可以分离出来。

以下代码请仔细认知

我们可以很清晰的观察到,k的生命周期并没有发生变化,只是当k = null;后,变量k不再指向堆中的有效地址了。

现在我们明白了,因为值类型变量直接在Stack中保存了数据,因此在生命周期结束前数据不能被任何形式的销毁,而引用类型变量在Heap中保存数据,所以赋值null其实是将对应在Heap中的数据销毁而不是结束变量的生命周期。