發布日期:2007.7.6 作者:Anytao
2007 Anytao.com ,原創作品,轉貼請註明作者和出處。
本文將介紹以下內容:
- 按值傳遞與按引用傳遞深論
- ref和out比較
- 參數應用淺析
接上篇繼續,『第十一回:參數之惑---傳遞的藝術(上)』
4.2 參考型別參數的按值傳遞
當傳遞的參數為參考型別時,傳遞和操作的是指向對象的引用,這意味著方法操作可以改變原來的對象,但是值得思考的是該引用或者說指標本身還是按值傳遞的。因此,我們在此必須清楚的瞭解以下兩個最根本的問題:
- 參考型別參數的按值傳遞和按引用傳遞的區別?
- string類型作為特殊的參考型別,在按值傳遞時表現的特殊性又如何解釋?
首先,我們從基本的理解入手來瞭解參考型別參數按值傳遞的本質所在,簡單的說對象作為參數傳遞時,執行的是對對象地址的拷貝,操作的是該拷貝地址。這在本質上和實值型別參數按值傳遞是相同的,都是按值傳遞。不同的是實值型別的“值”為類型執行個體,而參考型別的“值”為引用地址。因此,如果參數為參考型別時,在調用方代碼中,可以改變引用的指向, 從而使得原對象的指向發生改變,如例所示:
參考型別參數的按值傳遞
// FileName : Anytao.net.My_Must_net
// Description : The .NET what you should know of arguments.
// Release : 2007/07/01 1.0
// Copyright : (C)2007 Anytao.com http://www.anytao.com
using System;
namespace Anytao.net.My_Must_net
{
class Args
{
public static void Main()
{
ArgsByRef abf = new ArgsByRef();
AddRef(abf);
Console.WriteLine(abf.i);
}
private static void AddRef(ArgsByRef abf)
{
abf.i = 20;
Console.WriteLine(abf.i);
}
}
class ArgsByRef
{
public int i = 10;
}
}
因此,我們進一步可以總結為:按值傳遞的實質的是傳遞值,不同的是這個值在實值型別和參考型別的表現是不同的:參數為實值型別時,“值”為執行個體本身,因此傳遞的是執行個體拷貝,不會對原來的執行個體產生影響;參數為參考型別時,“值”為對象引用,因此傳遞的是引用地址拷貝,會改變原來對象的引用指向,這是二者在統一概念上的表現區別,理解了本質也就抓住了根源。關於實值型別和參考型別的概念可以參考《第八回:品味類型---實值型別與參考型別(上)-記憶體有理》《第九回:品味類型---實值型別與參考型別(中)-規則無邊》《第十回:品味類型---實值型別與參考型別(下)-應用征途》,相信可以通過對系列中的實值型別與參考型別的3篇的理解,加深對參數傳遞之惑的昭雪。
瞭解了參考型別參數按值傳遞的實質,我們有必要再引入另一個參數傳遞的概念,那就是:按引用傳遞,通常稱為引用參數。這二者的本質區別可以小結為:
- 參考型別參數的按值傳遞,傳遞的是參數本身的值,也就是上面提到的對象的引用;
- 按引用傳遞,傳遞的不是參數本身的值,而是參數的地址。如果參數為實值型別,則傳遞的是該實值型別的地址;如果參數為參考型別,則傳遞的是對象引用的地址。
關於引用參數的詳細概念,我們馬上就展開來討論,不過還是先分析一下string類型的特殊性,究竟特殊在哪裡?
關於string的討論,在本人拙作《第九回:品味類型---實值型別與參考型別(中)-規則無邊》已經有了討論,也就是開篇陳述的本文成文的曆史,所以在上述分析的基礎上,我認為應該更能對第九回的問題,做以更正。
string本身為參考型別,因此從本文的分析中可知,對於形如
static void ShowInfo(string aStr){...}
的傳遞形式,可以清楚的知道這是按值傳遞,也就是本文總結的參考型別參數的按值傳遞。因此,傳遞的是aStr對象的值,也就是aStr引用指標。接下來我們看看下面的樣本來分析,為什麼string類型在傳遞時表現出特殊性及其產生的原因?
// FileName : Anytao.net.My_Must_net
// Description : The .NET what you should know of arguments.
// Release : 2007/07/05 1.0
// Copyright : (C)2007 Anytao.com http://www.anytao.com
using System;
namespace Anytao.net.My_Must_net
{
class how2str
{
static void Main()
{
string str = "Old String";
ChangeStr(str);
Console.WriteLine(str);
}
static void ChangeStr(string aStr)
{
aStr = "Changing String";
Console.WriteLine(aStr);
}
}
}
下面對上述樣本的執行過程簡要分析一下:首先,string str = "Old String"產生了一個新的string對象,表示:
然後執行ChangeStr(aStr),也就是進行參考型別參數的按值傳遞,我們強調說這裡傳遞的是參考型別的引用值,也就是地址指標;然後調用ChangeStr方法,過程aStr = "Changing String"完成了以下的操作,先在新的一個地址產生一個string對象,該新對象的值為"Changing String",引用地址為0x06賦給參數aStr,因此會改變aStr的指向,但是並沒有改變原來方法外str的引用地址,執行過程可以表示為:
因此執行結果就可想而知,我們從分析過程就可以發現string作為參考型別,在按值傳遞過程中和其他參考型別是一樣的。如果需要完成ChangeStr()調用後,改變原來str的值,就必須使用ref或者out修飾符,按照按引用傳遞的方式來進行就可以了,屆時aStr = "Changing String"改變的是str的引用,也就改變了str的指向,具體的分析希望大家通過接下來的按引用傳遞的揭密之後,可以自行分析。
4.3 按引用傳遞之ref和out
不管是實值型別還是參考型別,按引用傳遞必須以ref或者out關鍵字來修飾,其規則是:
- 方法定義和方法調用必須同時顯示的使用ref或者out,否則將導致編譯錯誤;
- CRL允許通過out或者ref參數來重載方法,例如:
// FileName : Anytao.net.My_Must_net
// Description : The .NET what you should know of arguments.
// Release : 2007/07/03 1.0
// Copyright : (C)2007 Anytao.com http://www.anytao.com
using System;
namespace Anytao.net.My_Must_net._11_Args
{
class TestRefAndOut
{
static void ShowInfo(string str)
{
Console.WriteLine(str);
}
static void ShowInfo(ref string str)
{
Console.WriteLine(str);
}
}
}
當然,按引用傳遞時,不管參數是實值型別還是參考型別,在本質上也是相同的,這就是:ref和out關鍵字將告訴編譯器,方法傳遞的是參數地址,而不是參數本身。理解了這一點也就抓住了按引用傳遞的本質,因此根據這一本質結論我們可以得出以下更明白的說法,這就是:
- 不管參數本身是實值型別還是參考型別,按引用傳遞時,傳遞的是參數的地址,也就是執行個體的指標。
- 如果參數是實值型別,則按引用傳遞時,傳遞的是實值型別變數的引用,因此在效果上類似於參考型別參數的按值傳遞方式,其實質可以分析為:實值型別的按引用傳遞方式,實現的是對實值型別參數執行個體的直接操作,方法調用方為該執行個體分配記憶體,而被呼叫者法操作該記憶體,也就是實值型別的地址;而參考型別參數的按值傳遞方式,實現的是對參考型別的“值”引用指標的操作。例如:
// FileName : Anytao.net.My_Must_net
// Description : The .NET what you should know of arguments.
// Release : 2007/07/06 1.0
// Copyright : (C)2007 Anytao.com http://www.anytao.com
using System;
namespace Anytao.net.My_Must_net
{
class TestArgs
{
static void Main(string[] args)
{
int i = 100;
string str = "One";
ChangeByValue(ref i);
ChangeByRef(ref str);
Console.WriteLine(i);
Console.WriteLine(str);
}
static void ChangeByValue(ref int iVlaue)
{
iVlaue = 200;
}
static void ChangeByRef(ref string sValue)
{
sValue = "One more.";
}
}
}
如果參數是參考型別,則按引用傳遞時,傳遞的是引用的引用而不是引用本身,類似於指標的指標概念。樣本只需將上述string傳遞樣本中的ChangeStr加上ref修飾即可。
下面我們再進一步對ref和out的區別做以交代,就基本闡述清楚了按引用傳遞的精要所在,可以總結為:
- 相同點:從CRL角度來說,ref和out都是指示編譯器傳遞執行個體指標,在表現行為上是相同的。最能證明的樣本是,CRL允許通過ref和out來實現方法重載,但是又不允許通過區分ref和out來實現方法重載,因此從編譯角度來看,不管是ref還是out,編譯之後的代碼是完全相同的。例如:
// FileName : Anytao.net.My_Must_net
// Description : The .NET what you should know of arguments.
// Release : 2007/07/03 1.0
// Copyright : (C)2007 Anytao.com http://www.anytao.com
using System;
namespace Anytao.net.My_Must_net._11_Args
{
class TestRefAndOut
{
static void ShowInfo(string str)
{
Console.WriteLine(str);
}
static void ShowInfo(ref string str)
{
Console.WriteLine(str);
}
static void ShowInfo(out string str)
{
str = "Hello, anytao.";
Console.WriteLine(str);
}
}
}
編譯器將提示: “ShowInfo”不能定義僅在 ref 和 out 上有差別的重載方法。
- 不同點:使用的機制不同。ref要求傳遞之前的參數必須首先顯示初始化,而out不需要。也就是說,使用ref的參數必須是一個實際的對象,而不能指向null;而使用out的參數可以接受指向null的對象,然後在調用方法內部必須完成對象的實體化。
5. 結論
完成了對實值型別與參考型別的論述,在這些知識積累的基礎上,本文期望通過深入的論述來進一步的分享參數傳遞的藝術,解開層層疑惑的面紗。從探討問題的角度來說,參數傳遞的種種誤區其實根植與對實值型別和參考型別的本質理解上,因此完成了對類型問題的探討再進入參數傳遞的迷宮,我們才會更加遊刃有餘。我想,這種探討問題的方式,也正是我們追逐問題的方式,深入進入.NET的進階殿堂是繞不開這一選擇的。
參考文獻
(USA)Jeffrey Richter, Applied Microsoft .NET Framework Programming
(USA)David Chappell, Understanding .NET
溫故知新
[開篇有益]
[第一回:恩怨情仇:is和as]
[第二回:對抽象編程:介面和抽象類別]
[第三回:曆史糾葛:特性和屬性]
[第四回:後來居上:class和struct]
[第五回:深入淺出關鍵字---把new說透]
[第六回:深入淺出關鍵字---base和this]
[第七回:品味類型---從一般型別系統開始]
[第八回:品味類型---實值型別與參考型別(上)-記憶體有理]
[第九回:品味類型---實值型別與參考型別(中)-規則無邊]
[第十回:品味類型---實值型別與參考型別(下)-應用征途]
[第十一回:參數之惑---傳遞的藝術(上)]
2007 Anytao.com
原創作品,轉貼請註明作者和出處,留此資訊。
本貼子以“現狀”提供且沒有任何擔保,同時也沒有授予任何權利。
This posting is provided "AS IS" with no warranties, and confers no rights.