參考博客:
shyleoking
前言
首先我們知道值類型存儲在棧(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的生命周期結束了
}
下面的圖描述了這些變量的生命周期和堆棧的存儲

把值類型設置為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的生命周期結束了???
//m的生命周期結束了
}//i的生命周期結束了
//k的生命周期結束了
}
現在我們看下代碼到第8行時的棧:

這個時候k在棧底,如果我們想釋放k,就要先釋放m和i。
所以這樣操作就破壞了棧的先進后出的規則,所以會出錯,編輯就會報錯。
引用類型為什么可以設置為null
上述的代碼改為如下形式:
static void Main(string[] args)
{
List<int> k = new List<int>();//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還是分配在棧中,但實際存放List實例的區域是在堆中。對List的實例使用,是通過在棧中的變量k來間接的指向的。所以就把對象引用null和生命周期兩個概念可以分離出來。
所以就算我們把k = null,也只是讓變量k不再指向堆中的有效地址了,但其生命周期並沒有發生變化。
現在我們明白了,因為值類型變量直接在棧中保存了數據,因此在生命周期結束前數據不能被任何形式的銷毀,而引用類型變量在堆中保存數據,所以賦值null其實是將對應在堆中的數據銷毀而不是結束變量的生命周期。
