本文由DevDiv Vincent原创翻译,转载请注明出处,
欢迎关注我的微博,更多精彩android/ios/wp/win8内容及时推送
http://weibo.com/xueyw
http://t.qq.com/ixueyw
原文地址:http://www.yoda.arachsys.com/csharp/parameters.html

很多人对c#的参数传递感到困惑,尤其是引用类型。

.net中主要有两种类型:引用类型和值类型。
他们工作方式、表现不同,很多人就是因为对他们理解不够,导致对参数传递产生很多困惑。

什么是引用类型?
引用类型即对象是一个引用,而不是数据本身。
比如以下代码:
StringBuilder sb = new StringBuilder();
这里我们定义了一个变量sb,创建一个StringBuilder对象,赋值对象的引用给sb。
sb的值不是对象本身,而是对象的引用。
再看以下代码:
StringBuilder first = new StringBuilder();
first.Append("hello");
StringBuilder second = first;
Console.WriteLine(second); // Prints hello
这里我们定义变量first,创建StringBuilder对象,然后把这个对象的引用赋值给first。
接下来,我们我们把first赋值给second。这时候first和second指向相同的对象。如果通过调用firat.Append修改对象的值,另一个变量(second)可以感知到这种变化。
StringBuilder first = new StringBuilder();
first.Append("hello");
StringBuilder second = first;
first.Append(" world");
Console.WriteLine(second); // Prints hello world
然而,他们仍旧是完全独立的变量。改变变量值(变量本身)并不影响另一个。如果修改first的值,指向另一个字符串或者设置为null,那么second的值并不跟着变化。
StringBuilder first = new StringBuilder();
first.Append("hello");
StringBuilder second = first;
first.Append(" world");
first = new StringBuilder("goodbye");
Console.WriteLine(first); // Prints goodbye
Console.WriteLine(second); // Still prints hello world
Class类型,interface类型,delegate类型,array类型都是引用类型

什么是值类型?
引用类型中,变量并不包含数据本身,而是通过某种方式指向数据,而值类型则不同。值类型直接包含数据。值类型赋值包括数据复制过程。举一个简单的例子:
public struct IntHolder
{
public int i;
}
如果IntHolder类型的变量,那么变量包含数据本身,这里就是简单的int类型数据。赋值会导致数据复制发生,举例如下:
IntHolder first = new IntHolder();
first.i = 5;
IntHolder second = first;
first.i = 6;
Console.WriteLine (second.i);
输出为5
这里second.i的值为5,因为对second构造的时候,把first.i的值也复制过来了,那时候first.i的值为5.
简单类型(如float, int, char),枚举类型,结构体类型都是值类型。

主要指出的是,很多类型(比如string)看起来像是值类型的,但是他们是引用类型的。实际上他们是immutable类型,即当实例构造完成后,就不能改变了。这让引用类型可以类型值类型一样工作。比如,你拥有一个指向immutable类型的引用,你可以把它作为方法的返回值或者作为参数传递到其他方法中去,即便如此,也能确保它不会被偷偷改变。这就是为什么string.Replace不会改变string内容,二十狗仔一个新的实例。
与immutable类型对应的是mutable类型,比如ArrayList。如果某个方法返回ArrayList引用,那么调用者可以往列表中添加条目。


测试你对引用类型和值类型的理解。。。
不同类型的参数
C#中支持四种不同的参数类型:值参数(默认),引用参数(ref修饰符),输出参数(out修饰符)以及参数数组(params修饰符)。你可以使用任意一种。

值参数
缺省形况下参数是按照值传递的。即函数定义处是新分配一段存储空间,创建新的变量,赋值为函数调用时候的参数值。如果你修改了这个参数,它并不影响调用者传递的变量。
举例如下:
void Foo (StringBuilder x)
{
x = null;
}

...

StringBuilder y = new StringBuilder();
y.Append ("hello");
Foo (y);
Console.WriteLine (y==null);
数据结果为False
Y的值不会因为x赋值为null而改变。需要注意的是引用类型的变量的值是一个引用(即变量本身是引用,值参数传递的是引用的值),所以如果改变对象内的数据成员,那么函数调用者那里是可以看到这种变化的。举例说明:

void Foo (StringBuilder x)
{
x.Append (" world");
}

...

StringBuilder y = new StringBuilder();
y.Append ("hello");
Foo (y);
Console.WriteLine (y);
输出结果为hello world
这里我们把y按值传给Foo,Foo的参数x把y的值copy过来,而x和y都是引用类型,即他们是指向指向的引用,所以他们指向共同的数据,所以对x的改变会反映到y上去。

接下来考虑一下如果按值传递值类型会怎么样。以我们定义个strcut IntHolder为例:
void Foo (IntHolder x)
{
x.i=10;
}

...

IntHolder y = new IntHolder();
y.i=5;
Foo (y);
Console.WriteLine (y.i);
输出结果为5
当Foo调用时候,i为5。当我们改变x的i值时候,由于x和y都是值类型,他们本身就包含自己的数据,所以不会相互影响。

引用参数
在函数调用时候引用参数并不传递变量值,它用变量本身。它不像值参数那样申请新的存储空间,保留数值。举例说明如下:
void Foo (ref StringBuilder x)
{
x = null;
}

...

StringBuilder y = new StringBuilder();
y.Append ("hello");
Foo (ref y);
Console.WriteLine (y==null);
输出结果为True
由于y是按照引用传递的(Foo的函数变量参数引用参数),所以对x的改变立马会在y上表现出来。在上例中,y的值最后会变为null。

接下来,我们以之前定义的结构体为例,说明应用参数
void Foo (ref IntHolder x)
{
x.i=10;
}

...

IntHolder y = new IntHolder();
y.i=5;
Foo (ref y);
Console.WriteLine (y.i);
数据结果为10
两个变量共享存储空间,所以对x的的改变会反映到y上面去。

问题:引用方式传递值对象与值参数方式传递引用对象有什么区别?
你可能已经注意到了,按照引用参数方式传struct与按照值参数方式传引用可以达到同样效果。但是他们有什么区别呢?举例说明如下:
void Foo (??? IntHolder x)
{
x = new IntHolder();
}

...

IntHolder y = new IntHolder();
y.i=5;
Foo (??? y);
这段代码,假设IntHolder为结构体,并且参数是按照引用方式传递(把???替换为ref),那么y变量本身会发生变化(y的地址可能发生改变)。
如果IntHolder定义为class,参数按照值来传递(把代码中的???去掉),那么y的值(及其地址)是不会改变的。

简单总结,按照ref传递参数,你可以改变变量本身(即它的地址会变化),而按照值传递引用类型,那么你只能改变变量的数据成员,不能改变变量本身。

输出参数
与引用参数类似,输出参数也不会创建新的存储空间,而是使用调用时候变量的地址。
举例说明:
void Foo (out int x)
{
// Can't read x here - it's considered unassigned

// Assignment - this must happen before the method can complete normally
x = 10;

// The value of x can now be read:
int a = x;
}

...

// Declare a variable but don't assign a value to it
int y;

// Pass it in as an output parameter, even though its value is unassigned
Foo (out y);

// It's now assigned a value, so we can write it out:
Console.WriteLine (y);
输出结果为10

参数数组
举例如下:

void ShowNumbers (params int[] numbers)
{
foreach (int x in numbers)
{
Console.Write (x+" ");
}
Console.WriteLine();
}

...

int[] x = {1, 2, 3};
ShowNumbers (x);
ShowNumbers (4, 5);
输出结果为
1 2 3
4 5
第一个调用中,变量x是按照值传递的,传递的是值类型。第二种也是按照值传递,但是传递的是引用类型(动态构造的array)。


Regards
Vincent
http://weibo.com/xueyw
http://t.qq.com/ixueyw