天天看點

【.NET版月經問題】之二【引用類型參數就是按引用傳遞嗎?】

這個問題其實算不得月經帖問題,已經成周經甚至日經了...隔幾天就有人問,然後總是大部分人站在自以為正确的角度大談引用與指針的相似性,假如不幸這參數是string類型無一例外會被人扯到string的“不可變性”上去,而且每次都有扛着紅星星的高人大撒煙霧彈,讓提問者一頭霧水...

這問題産生的來源是可惡的C/C++指針...很多人指點新手(包括很多教育訓練機構的講師)講到“引用”這一.NET的特殊資料類型時,都直接一句:相當于(甚至說“就是”)C/C++的指針——其實我也說過,慚愧...不幸的是兩者看似相似,本質卻不同...事實上這個引用和Java的引用才是非常相似的東西,幾乎一樣...

這就帶來一個問題...凡是學過C/C++指針的,總有很多人想當然地認為傳遞一個引用類型的參數就是傳遞這個引用類型執行個體的引用——我還沒說指針呢——看起來似乎是這樣...大錯特錯!

在.NET中,除非顯式以ref或out聲明傳遞參數,否則所有類型的參數都是按值傳遞的!引用類型也不會例外!

這句話我不知道回答過多少遍,不過看起來沒什麼效果...每次一出此類問題,照舊大堆人往按引用傳遞和string不可變上引...

那麼先來看看執行個體吧...值類型一般不會有疑義,就不提了...

class test 

public string s = null; 

public int i = 0; 

public void run() 

    test instance = new test(); 

    instance.s = "first"; 

    instance.i = 1; 

    callByValue(instance); 

public void callByValue(test t) 

    t.s = "changed"; 

    t.i = 2; 

class test { public string s = null; public int i = 0; } public void run() { test instance = new test(); instance.s = "first"; instance.i = 1; callByValue(instance); } public void callByValue(test t) { t.s = "changed"; t.i = 2; }

這段代碼就是廣泛會被誤認為按引用傳遞實際上是按值傳遞的參數傳遞方式...它的執行結果非常明顯,參數引用類型test的執行個體instance的成員s和i一定會被更改,是以看起來它似乎确實是按引用傳遞的...但是,錯的!這個參數傳遞的是該參數執行個體的一個副本!

引用類型執行個體的副本是什麼呢?就是這個instance的引用的副本...也就是說,這個時候在棧上,原來的instance的引用還在,傳遞給callByValue方法的參數t是棧上instance的引用的copy...這個copy引用指向托管堆上instance值的位址,是以一改俱改...是以表象似乎一樣,但和C/C++傳遞指針的方式本質是差别巨大的...

我們把callByValue方法稍作修改,如下...

    test tmp=new test(); 

    tmp.s = "changed"; 

    tmp.i = 2; 

    t = tmp; 

public void callByValue(test t) { test tmp=new test(); tmp.s = "changed"; tmp.i = 2; t = tmp; }

結果很顯然,不會變...instance和instance的成員都不會變,原因呢?最常見的解釋是作用域不同,tmp隻在callByValue方法體中存活,是以呢,出了這個方法體就不起作用了...胡說八道!出了方法體tmp廢棄了,那instance應該是null才對啊?!什麼思維邏輯...其實很簡單,上面說了,這個傳遞進來的引用隻是個副本,修改這個副本不會對在棧上的instance引用有絲毫影響,新構造的執行個體也跟托管堆上instance的值毫不相幹...當退出方法體時,這個副本随即被當作垃圾廢棄,instance和它的成員自然不會變...

看看另一個方法...

public void callByReference(ref test t) 

public void callByReference(ref test t) { test tmp=new test(); tmp.s = "changed"; tmp.i = 2; t = tmp; }

這就改變了...tmp被廢棄了,但是instance卻改變了...因為這時傳遞進去的是instance的引用本身,自然在t=tmp;時instance的引用被修改指向tmp在托管堆上的值...

最後來看看“特殊”的string...在被當做參數傳遞時,string一點也不特殊...

string instance = "first"; 

    callByReference(instance); 

public void callByValue(string s) 

    s = "changed"; 

public void callByReference(ref string s) 

public void run() { string instance = "first"; callByValue(instance); callByReference(instance); } public void callByValue(string s) { s = "changed"; } public void callByReference(ref string s) { s = "changed"; }

callByValue方法一定不會更改instance的值,而callByReference方法一定會更改instance的值...原因和上面一樣,前者傳遞的是instance的引用的副本,後者傳遞的是instance的引用本身...跟什麼“不可變性”、“字元串駐留”毫不相幹...

此處存疑:

此處有個問題,string傳到下面方法的時候,确實是傳遞了一個引用的副本。 

public void callByValue(string s)  

{  

    s = "changed";  

}

那麼既然是引用的副本,那麼指向的實際内容就必定是是同一個位址,同一個資料。然後再對這個引用副本指派,理論上是指向的内容會被更改,但是實際上,正是由于string的特殊性,他不能直接在該位址空間上做修改,而隻能開辟新空間,是以導緻引用副本指向新開辟的空間。

是以這裡和string的特殊性是有關系的。

本文轉自cnn23711151CTO部落格,原文連結:<b>http://blog.51cto.com/cnn237111/601458</b> ,如需轉載請自行聯系原作者

繼續閱讀