以引用的方式向方法传递参数
默认情况下,CLR假定所有方法都是传值的。对于值类型的实例来说,很好理解,就是传给方法这个实例的副本。对于引用类型来说,我说其实传的也是一个副本,只不过这是引用的副本,但由于这个副本也是指向这个引用类型的实例,所以我们在方法中对这个引用的副本所指向的实例的修改都是修改的是实例本身。如面代码就是修改引用的副本指向的对象:
public yinyong yy=new yinyong(2,"2"); //yinyong是自定义类
MakeYinYong(yy); //这里面的变量yy是引用yy的副本,但它同样指向上面new出的yinyong的实例
public void MakeYinYong(yinyong y)
{
y.Age = 1;
y.Name = "1";
}
修改之后yy.Age就变为了1 yy.Name就变为了"1"
在C#中可以使用关键字ref或out来以传引用(传址)的方式向方法传递参数。 关于ref out见这里的1
这时就不是副本了,而是变量真正的地址,对于值类型可以理解为传递的是实例本身,对于引用类型可以理解为传递的是引用本身。如下代码就是修改引用指向的对象:
public yinyong yy=new yinyong(2,"2"); //yinyong是自定义类
MakeYinYong(ref yy) //这里面的变量yy是引用yy本身
public void MakeYinYong(ref yinyong y)
{
y.Age = 1;
y.Name = "1";
}
修改之后yy.Age就变为了1 yy.Name就变为了"1"
所以对于引用类型而言,如果在方法里仅仅是修改引用所指向的对象,是不是用传引用的方式是没有什么区别的。 但如果需要交换两个引用类型的对象的话,就有区别了,如下:
public yinyong yy1=new yinyong(1,"1"); public yinyong yy3=new yinyong(3,"3");
public yinyong yy2=new yinyong(2,"2"); public yinyong yy4=new yinyong(4,"4");
public void Swap(ref yinyong y1, ref yinyong y2) public void Swap(yinyong y3,yinyong yy4)
{ {
yinyong temp = y1; yinyong temp = y3;
y1 = y2; y3 = y4;
y2 = temp; y4 = temp;
} }
Swap(ref yy1,ref yy2); Swap(yy3,yy4);
label1.text=yy1.Age+yy2.Age; label2.text=yy3.Age+yy4.Age;
//将换成功 //交换失败
使用了ref的交换会交换成功就不说了, 没有使用ref的交换没有成功,是因为Swap(yy3,yy4);里的yy3与yy4只是真正的引用yy3与yy4的副本,所以交换的仅仅是副本yy3与yy4,但在使用时label2.text=yy3.Age+yy4.Age; 这里的yy3与yy4是真正的引用,它们并没有被交换。
另外,上面我们看到两个Swap方法除了参数有无ref外并无区别,其实CLR允许根据有无out ref进行重载,即可以根据是传值还是传址进行重载。
ref必须初始化参数,可以在方法里读取参数的值及修改参数 out不需要初始化参数,必须在方法里初始化参数但不能读取参数的值
还有就是对于以引用的方式传给方法的变量,它的类型必须与方法签名中声明的类型相同, 如把上面的改成
public void Swap(ref Object y1, ref Object y2)
{
Object temp = y1;
y1 = y2;
y2 = temp;
}
Swap(ref yy1,ref yy2) 是无法通过编译的,尽管Object是所有类型的父类,但在引用的方式下是不行的。
可选参数和命名实参(C#4.0的新特性)
C#4.0中设计一个方法的参数时,可为部分或全部参数分配默认值。然后,调用这些方法的代码可以选择不指定部分实参,接受其默认值。编译器是按从左向右的顺序依次赋实参,但有时我们会想如果我们使用可选参数的话势必会出现实参数与形参数不同的情形,这要怎么处理呢? 其实在C#4.0的方法参数中可选参数必须位于没有默认值的参数之后,所以我们不用担心我们设置的实参没有赋予给没有默认值的形参。 对于后面的可选参数,如果我们只是想其中的几个不使用其默认值,我们还可以在方法的调用处使用命名参数,这样我们就不用写出全部可选参数的实参了。如下代码:
方法: public void Print(int i,int j,string s1,string s2,int i1=1,string s3="3",DateTime dt=default(DateTime),Guid guid=new Guid())
调用方法: Print(1,2,"3","4",s3:"我不想使用默认值了",dt:DateTime.Now) *
可为方法、构造函数、有参属性(索引器)等的参数指定默认值。
如果参数用ref或out关键字进行了标识,就不能设置默认值。但调用时使用命名实参,如下:
方法:private void M(ref int i){ }
调用方法: int a=5; M(i:ref a);
如以上*处所示,调用方法时,命名实参只能出现在实参列表的尾部。
还有一种可能性要注意,当被调用的方法位于另一个程序集里,当被调用的方法修改了某个参数的默认值,那么我们调用该方法的程序集需要重新编译。
向方法传递可变数量的参数
可以在方法参数中使用params关键字。但注意的是params关键字只能应用于方法签名中的最后一个参数,而且这个参数只能是一个任何类型的一维数组。
我们可以来看加params和不加params的区别
public void Params1(string s,params int[] ints)
{
int sum = 0;
foreach (var item in ints)
{
sum += item;
}
Label1.Text = s + sum.ToString();
}
public void Params2(string s, int[] ints)
{
int sum = 0;
foreach (var item in ints)
{
sum += item;
}
Label1.Text = s + sum.ToString();
}
方法的声明除了params的区别以外没有任何区别,但调用的时候就不一样了
Params1("和是:", 1, 2, 3, 4, 5, 6, 7, 8, 9);
Params2("和是:", new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9 });
可以看到第一种更方便一些,而实际上我们用第一种方式时C#编译器是将其代码编译成和第二种方式一样的IL代码。
对于第一种方式当没有参数要传递的时候可以传递null Params1("没有传递参数", null); 这样C#编译器将不会构造一个数组。
public void Params(params Object[] objects) {...} 这样我们就可以传递任何数量任何类型的参数了
Params(new Object(),5,"Richard",new Random());
但由于C#编译器要构造数组,所以会有一些额外的性能损失。所以对于一些常用的方法我们还是不要只是定义包含params关键字的方法,而要重载一些不包含params关键字的方法,这样只有在很特殊的情况下才调用包含了params关键字的方法。