C# 按值傳遞和按引用傳遞
一、引言
C#中引數的傳遞方式可以分為兩類,按值傳遞和按引用傳遞。如果再根據引數的型別進行細分,大致可以分為如下四種:
- 值型別的按值傳遞
- 引用型別的按值傳遞
- 值型別的按引用傳遞
- 引用型別的按引用傳遞
string型別作為一種特殊的引用型別,部分人認為在作為引數進行傳遞的時候,它的表現與其他的引用型別是不一致的。但是透過現象看本質,我們深入分析一下,就會發現,在作為引數進行傳遞方面,string型別與其他的引用型別是沒有區別的。
二、四種傳遞方式解析
1. 值型別的按值傳遞
按值傳遞時,傳遞過去的是該值型別例項的一個拷貝。
1 public static int Add(int i,int j) 2 { 3 i = i + 1; 4 Console.WriteLine(i); 5 return i + j; 6 } 7 ...... 8 static void Main(string[] args) 9 { 10 int a = 1; 11 Add(a,5); 12 Console.WriteLine(a); 13 }
執行結果如下:
2. 引用型別的按值傳遞
按值傳遞時,傳遞過去的是該引用型別例項的引用的一個拷貝,這樣說可能不是很清楚,而且容易引起誤解。所謂引用,就是分配在棧上的一小塊記憶體區域,裡面存放著該引用型別例項在託管堆上的地址。引用型別在按值傳遞的時候,其實就是把它的引用在棧上覆製出來一份,然後傳遞給方法。這樣就造成了棧上的兩個引用指向了託管堆上的同一個例項。所以這就可以解釋,如下兩個方法執行結果為什麼會不一致。
1 class Person 2 { 3 public string Name { get; set; } 4 } 5 ...... 6 static void ChangePerson(Person person) 7 { 8 person = new Person(); 9 person.Name = "Changing Name"; 10 Console.WriteLine(person.Name); 11 } 12 13 static void ChangeName(Person person) 14 { 15 person.Name = "Changing Name"; 16 Console.WriteLine(person.Name); 17 } 18 ...... 19 // 引用型別的按值傳遞 20 Person person = new Person() { Name="Old" }; 21 ChangeName(person); 22 Console.WriteLine(person.Name); 23 24 // 引用型別的按值傳遞 25 person = new Person() { Name = "Old" }; 26 ChangePerson(person);27 Console.WriteLine(person.Name);
運算結果如下:
3.值型別的按引用傳遞
按引用傳遞的時候是不存在拷貝這步操作的,眾所周知,值型別的例項是分配在棧上的,所以在按引用傳遞值型別的時候,其實是把該例項在棧上的地址,傳遞給了方法。
1 public static int Add(ref int i,int j) 2 { 3 i = i + 1; 4 Console.WriteLine(i); 5 return i + j; 6 } 7 ...... 8 // 值型別的按引用傳遞 9 int a = 1; 10 Add(ref a,5); 11 Console.WriteLine(a);
運算結果如下:
4.引用型別的按引用傳遞
引用型別的按引用傳遞過程,與值型別的相似,也不存在拷貝這步操作,只是將“該例項的引用”在棧上的地址,傳遞給了方法。
1 class Person 2 { 3 public string Name { get; set; } 4 } 5 ...... 6 static void ChangeNameRef(ref Person person) 7 { 8 person.Name = "Changing Name"; 9 Console.WriteLine(person.Name); 10 } 11 static void ChangePerson(ref Person person) 12 { 13 person = new Person(); 14 person.Name = "Changing Name"; 15 Console.WriteLine(person.Name); 16 } 17 // 引用型別的按引用傳遞 18 person = new Person() { Name = "Old" }; 19 ChangeNameRef(ref person); 20 Console.WriteLine(person.Name); 21 22 person = new Person() { Name = "Old" }; 23 ChangePerson(ref person); 24 Console.WriteLine(person.Name);
運算結果如下:
PS:有些人認為,引用型別的按引用傳遞感覺與按值傳遞沒什麼區別,給引用型別加上ref和out也沒什麼意義。通過上述的運算結果對比一下就可以知道,其實這種認識是錯誤的。
三、string型別在引數傳遞方面是否異於其他引用型別?
string型別作為一種特殊的引用型別,它的特殊性具體有哪些,與我們的主題關係不大,我們只需要瞭解它的一個特性即可:當string型別的值發生變化時,相當於執行其他引用型別的new操作,這種說法並不準確,我們在此處並不需要深究。(需要了解string型別的特殊性,請自行百度!)正是因為這個特性,才導致在按值傳遞的時候,string型別與其他引用型別在外在表現上會有少許不同。但是根據string型別的這個特性,其實string型別的按值傳遞與上述引用型別的按值傳遞的第二個方法的執行情況從表現到本質都是一致的。
1 // string型別按值傳遞 2 static void ChangeStr(string aStr) 3 { 4 aStr = "Changing"; 5 Console.WriteLine(aStr); 6 } 7 // string型別按引用傳遞 8 static void ChangeStrRef(ref string aStr) 9 { 10 aStr = "Changing"; 11 Console.WriteLine(aStr); 12 } 13 ...... 14 // 字串型別的按值傳遞 15 string str = "Old"; 16 ChangeStr(str); 17 Console.WriteLine(str); 18 19 // 字串型別的按引用傳遞 20 str = "Old"; 21 ChangeStrRef(ref str); 22 Console.WriteLine(str);
運算結果如下: