在標准的Dispose模式中(見前一篇博客“html">C#中標准Dispose模式的實現”),提到了需要及時釋放資源,卻並沒有進一步細說讓引用等於null是否有必要。
有一些人認為等於null可以幫助垃圾回收機制早點發現並標識對象是垃圾。其他人則認為這沒有任何幫助。是否賦值為null的問題首先在方法的內部被人提起。現在,為了更好的闡述提出的問題,我們來撰寫一個Winform窗體應用程序。如下:
private void button1_Click(object sender, EventArgs e)
{
Method1();
Method2();
}
private void button2_Click(object sender, EventArgs e)
{
GC.Collect();
}
private void Method1()
{
SimpleClass s = new SimpleClass("method1");
s = null;
//其它無關工作代碼(這條注釋源於回應回復的朋友的質疑)
}
private void Method2()
{
SimpleClass s = new SimpleClass("method2");
}
}
class SimpleClass
{
string m_text;
public SimpleClass(string text)
{
m_text = text;
}
~SimpleClass()
{
MessageBox.Show(string.Format("SimpleClass Disposed, tag:{0}", m_text));
}
}
先點擊按鈕1,再點擊按鈕2釋放,我們會發現:
q 方法Method2中的對象先被釋放,雖然它在Method1之後被調用;
q 方法Method2中的對象先被釋放,雖然它不像Method1那樣為對象引用賦值為null;
在CLR托管應用程序中,存在一個“根”的概念,類型的靜態字段、方法參數以及局部變量都可以作為“根”存在(值類型不能作為“根”,只有引用類型的指針才能作為“根”)。
上面的兩個方法中各自的局部變量,在代碼運行過程中會在內存中各自創建一個“根”.在一次垃圾回收中,垃圾回收器會沿著線程棧上行檢查“根”。檢查到方法內的“根”時,如果發現沒有任何一個地方引用了局部變量,則不管是否為變量賦值為null,都意味著該“根”已經被停止掉。然後垃圾回收器發現該根的引用為空,同時標記該根可被釋放,這也表示著Simple類型對象所占用的內存空間可被釋放。所以,在上面的這個例子中,為s指定為null絲毫沒有意義(方法的參數變量也是這種情況)。
更進一步的事實是,JIT編譯器是一個經過優化的編譯器,無論我們是否在方法內部為局部變量賦值為null,該語句都會被忽略掉:
view sourceprint?s =
null
;
在我們將項目設置為Release模式下,上面的這行代碼將根本不會被編譯進運行時內。
正式由於上面這樣的分析,很多人認為為對象賦值為null完全沒有必要。但是,在另外一種情況下,卻要注意及時為變量賦值為null。那就是類型的靜態字段。為類型對象賦值為null,並不意味著同時為類型的靜態字段賦值為null:
private void button1_Click(object sender, EventArgs e)
{
SimpleClass s = new SimpleClass("test");
}
private void button2_Click(object sender, EventArgs e)
{
GC.Collect();
}
}
class SimpleClass
{
static AnotherSimpleClass asc = new AnotherSimpleClass();
string m_text;
public SimpleClass(string text)
{
m_text = text;
}