我們知道,c#的數據類型分為值類型和引用類型。值類型包括基本類型(數值類型,bool類型等)、結構和枚舉,引用類型包括類、委托、數組等。在使用方法傳遞變量時,就需要了解值類型和引用類型作為參數傳遞的機制了。
先看下面一段代碼:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int aNumber = 10;
A a = new A();
a.Name = "aaaaaaaa";
Console.WriteLine(aNumber);
Console.WriteLine(a.Name);
ToDouble(aNumber);
ToDouble(a);
Console.WriteLine(aNumber);
Console.WriteLine(a.Name);
Console.ReadKey();
}
static void ToDouble(int number)
{
number *= 2;
}
static void ToDouble(A a)
{
a.Name += "ssssssss";
}
}
class A
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
}
輸出結果為:
10
10
10
1010
通過結果發現,值類型aNumber在經過函數處理後,再次輸出時值並沒有發生變化,而引用類型class A的屬性發生了變化,為什麼會發生這種情況?
數據類型在內存中的保存時,值類型保存在堆棧中,引用類型將它的值保存在托管堆中,在堆棧中保存的是對堆上地址的引用,在方法中傳遞參數時,所有類型都是傳遞堆棧中的位置,具體過程為先在堆棧上復制一個傳遞的對象,然後將該對象傳給參數,所以顯而易見,數值類型保存在堆棧中,作為參數傳遞時直接復制了數值的一個副本給方法,方法對該副本所做的任何操作都不會影響到該數值的原值,但是引用類型復制的是堆棧上對堆的引用,該引用指向的仍然是堆上的引用類型,所以對應用類型所做的操作都會引起其本身的改變。
下面我們再看一段代碼:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string aStr="10";
Console.WriteLine(aStr);
ToDouble(aStr);
Console.WriteLine(aStr);
Console.ReadKey();
}
static void ToDouble(string aStr)
{
aStr+=aStr;
}
}
}
輸出結果為:
10
10
string類型是引用類型,所以在方法中傳遞的仍然是堆棧中對string變量引用的副本,但是為什麼上面的代碼並沒有改變string變量的值呢?這裡我們就需要知道,在c#中,string是一個比較特殊的類型,微軟並不提倡我們些如下的代碼:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string aStr="aaaaa";
string aOtherStr="bbbbb";
string theyConcat=aStr+aotherStr;
}
}
}
上面的代碼首先聲明了一個變量aStr,然後將其與另一個字符串連接,在具體實現過程中,系統先在托管對中保存了aaaaa值,在進行連接運算時,先將aaaaa的值復制一份,然後在後面連接bbbbb,最後將得到的新值的地址賦給aStr,所以此時aaaaa並沒有在內存中刪除,直到垃圾回收器將其回收。比較好的做法是在需要進行連接字符串操作時,使用StringBulider類進行操作,它會直接在對象後面進行連接操作,而不會先將值復制一遍。
回到上面的方法來,當String類型作為參數傳遞時,系統會先在托管堆中將值復制一遍,堆棧上得到的地址指向這個新值,所以方法對string類型的操作並不影響到它的原有值。
這裡我們必然會想到,如果我需要使用一個方法改變值類型的值咋辦呢?比如說,當我們需要一個方法輸出多個值時,我們知道,當方法有返回值時,最後return回來的總是只有一個值,如果我需要將參數a、b同時傳入方法並得到兩個結果時,怎麼辦呢?此時就需要使用引用傳遞了。c#的方法使用了ref關鍵字,該關鍵字表示將參數作為引用類型傳遞,看下面的代碼:
class Program
{
static void Main(string[] args)
{
int a = 10;
int b = 50;
Console.WriteLine(a);
Console.WriteLine(b);
ToDouble(ref a,ref b);
Console.WriteLine(a);
Console.WriteLine(b);
Console.ReadKey();
}
static void ToDouble(ref int a,ref int b)
{
a *= 2;
b *= 2;
}
}
輸出結果:
10
50
20
100
通過這個方法,我們同時改變了a和b的值。
摘自 魯信的專欄