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);
运算结果如下: