原文鏈接:http://jonskeet.uk/csharp/parameters.html
<h2>序言:什么是引用類型(reference type)?</h2>
在C#中,有兩種主要的類型:引用類型(reference type)和值類型(value type)。
類,委托和接口屬于引用類型,結(jié)構(gòu)和枚舉屬于值類型。
它們所表現(xiàn)的行為不同,關(guān)于參數(shù)傳遞的很多疑惑都來(lái)自于大家不甚了解兩者之間的不同之處。下面是一些簡(jiǎn)要的解釋:
引用類型:它的值是一個(gè)引用,而不是該引用所指代的對(duì)象。比如說(shuō),考慮下面的代碼:
StringBuilder sb = new StringBilder();
這里,我們聲明了一個(gè)變量sb,創(chuàng)建了一個(gè)新的StringBuilder對(duì)象,并且把這個(gè)StringBuilder對(duì)象的引用賦值給了sb。sb的值<b>并不是</b>對(duì)象自身,它僅僅是一個(gè)引用。
引用類型的賦值很簡(jiǎn)單----被賦的值是一個(gè)表達(dá)式/變量:
StringBuilder first = new StringBuilder();
first.Append("hello");
StringBuilder second = first;
Console.WriteLine(second); // 打印hello
這里,我們聲明了一個(gè)變量first,創(chuàng)建了一個(gè)新的StringBuilder對(duì)象,并且賦值給了first作為引用,指向這個(gè)對(duì)象。然后,我們把first的值賦值給了second。這意味著,它們共同指向一個(gè)相同的對(duì)象。如果我們通過(guò)調(diào)用first.Append對(duì)這個(gè)對(duì)象的值進(jìn)行了修改,那么通過(guò)second可以看到這個(gè)修改:
StringBuilder first = new StringBuilder();
first.Append("hello");
StringBuilder second = first;
first.Append(" world");
Console.WriteLine(second); // 打印hello world
雖然如此,這兩個(gè)變量仍然是相互獨(dú)立的。改變first的值,讓其指向一個(gè)完全不同的對(duì)象(或者讓其值指向一個(gè)null引用)并不會(huì)影響second,也不會(huì)影響first自己所指向的對(duì)象:
StringBuilder first = new StringBuilder();
first.Append("Hello");
StringBuilder second = first;
first.Append(" world");
first = new StringBuilder("goodbye");
Console.WriteLine(first); // 打印goodbye
Console.WriteLine(second); // 仍然打印hello world
</br>
<h2>序言繼續(xù):什么是值類型(value type)</h2>
引用類型在變量和真正的數(shù)據(jù)之間隔離了一個(gè)間接層,值類型并不是。值類型的變量直接就包含了數(shù)據(jù)。對(duì)于值類型的賦值使得真實(shí)數(shù)據(jù)被復(fù)制了。例如:
public struct IntHolder {
public int i;
}
不論IntHolder類型的變量在哪里,這個(gè)變量的值都包含了所有數(shù)據(jù),在上述例子中,就是包含了一個(gè)整型數(shù)據(jù)。賦值對(duì)于值的拷貝如下:
IntHolder first = new IntHolder();
first.i = 5;
IntHolder second = first;
first.i = 6;
Console.WriteLine(second.i);
輸出的值為5。
這里,second.i的值為5,因?yàn)檫@是當(dāng)賦值second = first執(zhí)行的時(shí)候,first.i的值,也就是說(shuō),除了當(dāng)賦值語(yǔ)句執(zhí)行時(shí),second中的值和first中的值是獨(dú)立的。
簡(jiǎn)單類型(如float,int,char),enum類型和struct類型都是值類型。
注意,很多類型(比如string)似乎在某些情況下表現(xiàn)出是值類型,但是實(shí)際上是引用類型。這些是所謂的“不可變類型”(<i>immutable types</i>)。這意味著一旦一個(gè)實(shí)例已經(jīng)被構(gòu)建,就不能改變了。
這使得一個(gè)引用類型表現(xiàn)出了一些與值類型的<i>相似性</i>,尤其是,如果你有一個(gè)不可變類型的引用,你在從一個(gè)方法中返回它或者把它傳遞給另一個(gè)方法時(shí)會(huì)覺(jué)得安心,因?yàn)槟阒浪粫?huì)背著你自己發(fā)生改變。這是因?yàn)椋热?,string.Replace并不會(huì)改變調(diào)用它的字符串,而是會(huì)返回一個(gè)保存新字符串?dāng)?shù)據(jù)的新的實(shí)例。如果原始字符串被改變了,任何其他保存對(duì)這個(gè)字符串的引用都會(huì)發(fā)生變化-----這樣的需求很少見(jiàn)。
</br>
<h2>參數(shù)的不同類型</h2>
C#中有四種不同類型的參數(shù):值參數(shù)(默認(rèn)),引用參數(shù)(使用ref),輸出參數(shù)(使用out),和參數(shù)數(shù)組(使用params)。
<h3><i>你可以通過(guò)值類型和引用類型使用任何上述四種類型的參數(shù)</i></h3>
當(dāng)你看到"引用"或者"值"時(shí),你應(yīng)該非常清楚,這里所指的究竟是這個(gè)參數(shù)是引用參數(shù)還是值參數(shù),還是類型是值類型或是引用類型。如果你能夠分清這兩種情況,那就簡(jiǎn)單了。
</br>
<h3>值參數(shù)(Value parameters)</h3>
默認(rèn)情況下,參數(shù)是值參數(shù)。這意味著在方法聲明中,一個(gè)該變量的新的存儲(chǔ)區(qū)域被創(chuàng)建了。例如:
void Foo (StringBuilder x)
{
x = null;
}
...
StringBuilder y = new StringBuilder();
y.Append("hello");
Foo(y);
Console.WriteLine(y == null);
結(jié)果是False。
y的值并沒(méi)有因?yàn)閤被設(shè)置為null而改變。(注:x為null后,y仍然指向StringBuilder的實(shí)例)。
如果兩個(gè)引用類型的變量指向一個(gè)相同的對(duì)象,那么這個(gè)對(duì)象的改變會(huì)同時(shí)反映在這兩個(gè)引用上,如下:
Void Foo(StringBuilder x)
{
x.Append.(" world");
}
...
StringBuilder y = new StringBuilder();
y.Append("hello");
Foo (y);
Console.WriteLine(y); // 打印hello world
當(dāng)調(diào)用Foo后,y指向的StringBuilder對(duì)象包含"hello world",因?yàn)樵贔oo函數(shù)中,數(shù)據(jù)" world"通過(guò)x這個(gè)引用追加到了對(duì)象中。
現(xiàn)在,考慮當(dāng)值類型作為值參數(shù)傳遞的情況。正如之前所說(shuō),值類型的值就是數(shù)據(jù)本身??紤]下面的代碼:
void Foo (IntHolder x)
{
x.i = 10;
}
...
IntHolder y = new IntHolder();
y.i = 5;
Foo(y);
Console.WriteLine(y.i); // 打印5
當(dāng)Foo被調(diào)用后,結(jié)構(gòu)體x剛開(kāi)始i的值是5,隨后變成了10。Foo不知道變量y的任何信息,當(dāng)方法完成成后,y的值仍然是原先的5。
</br>
<h3>引用參數(shù)(Value parameters)</h3>
引用參數(shù)不傳遞變量的值,而是傳遞變量自身。引用參數(shù)需要ref修飾符作為聲明,這意味著當(dāng)你通過(guò)引用傳遞參數(shù)時(shí),你是永遠(yuǎn)知道自己在做什么的??聪旅娴睦樱瑑H僅改變了參數(shù)的傳遞,作為引用參數(shù)傳遞。
Void Foo(ref StringBuilder x)
{
x = null;
}
...
StringBuilder y = new StringBuilder();
y.Append("hello");
Foo (ref y);
Console.WriteLine(y == null);// 打印True
這里,由于y的引用自身被傳遞了,而不是其值被傳遞,對(duì)參數(shù)x的改變立刻影響了y自己。
現(xiàn)在考慮結(jié)構(gòu)體作為引用參數(shù)傳遞:
void Foo (ref IntHolder x)
{
x.i = 10;
}
...
IntHolder y = new IntHolder();
y.i = 5;
Foo (ref y);
Console.WriteLine(y.i); // 打印10
這兩個(gè)變量共享同一個(gè)存儲(chǔ)區(qū)域,所以對(duì)于x的改變對(duì)于y來(lái)說(shuō)是一樣的。
注:通過(guò)引用傳遞參數(shù)傳遞一個(gè)值對(duì)象和通過(guò)值參數(shù)方式傳遞一個(gè)引用對(duì)象的區(qū)別
你可能已經(jīng)注意到,在上一個(gè)例子中,通過(guò)引用參數(shù)方式傳遞一個(gè)結(jié)構(gòu)體時(shí),效果等同于通過(guò)值參數(shù)的方式傳遞一個(gè)類對(duì)象。但是,考慮下面的代碼:
Void Foo (??? IntHolder x)
{
x = new IntHolder();
}
...
IntHolder y = new IntHolder();
y.i = 5;
Foo(??? y);
現(xiàn)在,考慮以下情況:當(dāng)IntHolder是一個(gè)結(jié)構(gòu)體(值類型),參數(shù)是引用參數(shù)時(shí)(ref 代替代碼中的???)。當(dāng)調(diào)用了Foo(ref y)后,y的值是一個(gè)新的IntHolder中的i的值(y.i是0)。
當(dāng)IntHolder是一個(gè)類(引用類型),參數(shù)是值參數(shù)時(shí)(移除代碼中的???),y的值是不變的---它還是指向那個(gè)方法調(diào)用前對(duì)象。
</br>
<h3>輸出參數(shù)(Output parameters)</h3>
類似于引用參數(shù),輸出參數(shù)并不開(kāi)辟新的存儲(chǔ)區(qū)域。輸出參數(shù)需要out修飾符,聲明和調(diào)用時(shí)都需要。這也意味著你在通過(guò)輸出參數(shù)傳遞對(duì)象時(shí),你是完全清楚自己的行為的。
</br>
<h3>參數(shù)數(shù)組(Parameters arrays)</h3>