参数传递看似很简单的东西,但是真正理解它的本质却并不很容易,在看过《你必须知道的.net》一书后,使我从本质上明白了参数的传递。
值类型和引用类型的区别:
值类型(Value Type),值类型实例通常分配在线程的堆栈(stack)上,并且不包含任何指向实例数据的指针,因为变量本身就包含了其实例数据。
引用类型(Reference Type),引用类型实例分配在托管堆(managed heap)上,变量存储对值的内存地址的引用。
using System;
public class Test
{
static void Main()
{
//定义值类型和引用类型,并完成初始化
MyStruct myStruct = new MyStruct();
MyClass myClass = new MyClass();
//定义另一个值类型和引用类型,
//以便了解其内存区别
MyStruct myStruct2 = new MyStruct();
myStruct2 = myStruct;
MyClass myClass2 = new MyClass();
myClass2 = myClass;
}
}
内存概况
根据参数类型和传递方式不同,有以下4中不同的情况:
- 值类型参数的按值传递
- 引用类型参数的按值传递
- 值类型参数的按引用传递
- 引用类型参数的按引用传递
1.值类型的参数的按值传递
值类型实例传递的是该值类型实例的一个拷贝,因此被调用方法操作的是属于自己本身的实例拷贝,因此不影响原来调用方法中的实例值。
using System;
namespace Anytao.net.My_Must_net
{
class Args
{
public static void Main()
{
int a = 10;
Add(a);
Console.WriteLine(a); //输出结果为10
}
private static void Add(int i)
{
i = i + 10;
Console.WriteLine(i);
}
}
}
2.引用类型参数的按值传递
当传递的参数为引用类型时,传递和操作的是指向对象的引用,这意味着方法操作可以改变原来的对象,但是值得思考的是该引用或者说指针本身还是按值传递的。
using System;
namespace Anytao.net.My_Must_net
{
class Args
{
public static void Main()
{
ArgsByRef abf = new ArgsByRef();
AddRef(abf);
Console.WriteLine(abf.i); //输出结果为20
}
private static void AddRef(ArgsByRef abf)
{
abf.i = 20;
Console.WriteLine(abf.i);
}
}
class ArgsByRef
{
public int i = 10;
}
}
按值传递的实质的是传递值,不同的是这个值在值类型和引用类型的表现是不同的:参数为值类型时,“值”为实例本身,因此传递的是实例拷贝,不会对原来的实例产生影响;参数为引用类型时,“值”为对象引用,因此传递的是引用地址拷贝,会改变原来对象的引用指向,这是二者在统一概念上的表现区别,理解了本质也就抓住了根源。
3.string类型作为引用类型的特殊性
using System;
namespace Anytao.net.My_Must_net
{
class Test
{
private int i = 10;
static void Main()
{
string str = "Old String";
Test test=new Test();
ChangeStr(str);
ChangeObj(test);
Console.WriteLine(str); //输出结果为Old String
Console.WriteLine(test.i); //输出结果为10
}
static void ChangeObj(Test t)
{
t=new Test(); //这里将创建一个新的对象,所以改变它不会对原对象有影响
t.i=20;
}
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.按引用传递之ref和out
不管是值类型还是引用类型,按引用传递必须以ref或者out关键字来修饰,其规则是:
方法定义和方法调用必须同时显示的使用ref或者out,否则将导致编译错误;
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关键字将告诉编译器,方法传递的是参数地址,而不是参数本身。
如果参数是引用类型,则按引用传递时,传递的是引用的引用而不是引用本身,类似于指针的指针概念。