值类型和引用类型的区别:
值类型(Value Type),值类型实例通常分配在线程的堆栈(stack)上,并且不包含任何指向实例数据的指针,因为变量本身就包含了其实例数据。
引用类型(Reference Type),引用类型实例分配在托管堆(managed heap)上,变量存储对值的内存地址的引用。
using
public
{
}
- 值类型参数的按值传递
- 引用类型参数的按值传递
- 值类型参数的按引用传递
- 引用类型参数的按引用传递
1.值类型的参数的按值传递
值类型实例传递的是该值类型实例的一个拷贝,因此被调用方法操作的是属于自己本身的实例拷贝,因此不影响原来调用方法中的实例值。
using
namespace
{
}
2.引用类型参数的按值传递
当传递的参数为引用类型时,传递和操作的是指向对象的引用,这意味着方法操作可以改变原来的对象,但是值得思考的是该引用或者说指针本身还是按值传递的。
using
namespace
{
}
按值传递的实质的是传递值,不同的是这个值在值类型和引用类型的表现是不同的:参数为值类型时,“值”为实例本身,因此传递的是实例拷贝,不会对原来的实例产生影响;参数为引用类型时,“值”为对象引用,因此传递的是引用地址拷贝,会改变原来对象的引用指向,这是二者在统一概念上的表现区别,理解了本质也就抓住了根源。
3.string类型作为引用类型的特殊性
using
namespace
{
}
下面对上述示例的执行过程简要分析一下:首先,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
namespace
{
}
当然,按引用传递时,不管参数是值类型还是引用类型,按引用传递时,传递的是参数的地址,也就是实例的指针。ref和out关键字将告诉编译器,方法传递的是参数地址,而不是参数本身。
如果参数是引用类型,则按引用传递时,传递的是引用的引用而不是引用本身,类似于指针的指针概念。
Ref和Out的区别:
ref和out的区别在C# 中,既可以通过值也可以通过引用传递参数。通过引用传递参数允许函数成员更改参数的值,并保持该更改。若要通过引用传递参数, 可使用ref或out关键字。ref和out这两个关键字都能够提供相似的功效,其作用也很像C中的指针变量。它们的区别是:
1、使用ref型参数时,传入的参数必须先被初始化。对out而言,必须在方法中对其完成初始化。
2、使用ref和out时,在方法的参数和执行方法时,都要加Ref或Out关键字。以满足匹配。
3、out适合用在需要retrun多个返回值的地方,而ref则用在需要被调用的方法修改调用者的引用的时候。
注:在C#中,方法的参数传递有四种类型:传值(by value),传址(by reference),输出参数(by output),数组参数(by array)。传值参数无需额外的修饰符,传址参数需要修饰符ref,输出参数需要修饰符out,数组参数需要修饰符params。传值参数在方法调用过程中如果改变了参数的值,那么传入方法的参数在方法调用完成以后并不因此而改变,而是保留原来传入时的值。传址参数恰恰相反,如果方法调用过程改变了参数的值,那么传入方法的参数在调用完成以后也随之改变。实际上从名称上我们可以清楚地看出两者的含义--传值参数传递的是调用参数的一份拷贝,而传址参数传递的是调用参数的内存地址,该参数在方法内外指向的是同一个存储位置。
方法参数上的 ref 方法参数关键字使方法引用传递到方法的同一个变量。当控制传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。
若要使用 ref 参数,必须将参数作为 ref 参数显式传递到方法。ref 参数的值被传递到 ref 参数。
传递到 ref 参数的参数必须最先初始化。将此方法与 out 参数相比,后者的参数在传递到 out 参数之前不必显式初始化。
属性不是变量,不能作为 ref 参数传递。
如果两种方法的声明仅在它们对 ref 的使用方面不同,则将出现重载。但是,无法定义仅在 ref 和 out 方面不同的重载。
out
方法参数上的 out 方法参数关键字使方法引用传递到方法的同一个变量。当控制传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。
当希望方法返回多个值时,声明 out 方法非常有用。使用 out 参数的方法仍然可以返回一个值。一个方法可以有一个以上的 out 参数。
若要使用 out 参数,必须将参数作为 out 参数显式传递到方法。out 参数的值不会传递到 out 参数。
不必初始化作为 out 参数传递的变量。然而,必须在方法返回之前为 out 参数赋值。
属性不是变量,不能作为 out 参数传递。
网上有很多文章说ref 只传值,out传地址等等这种说法,好像不是非常的准确。以下是我做的实例代码,大家可以去试试:
public int RefValue(int i,ref int j)
{
int k = j;
j =222;
return i+k;
}
public int OutValue(int i, out int j)
{
j = 222;
return i + j;
}
private void cmdRef_Click(object sender, EventArgs e)
{
int m = 0;
MessageBox.Show(RefValue(1, ref m).ToString());
MessageBox.Show(m.ToString());
}
private void cmdOut_Click(object sender, EventArgs e)
{
int m;
MessageBox.Show(OutValue(1, out m).ToString());
MessageBox.Show(m.ToString());
}
借网上总结的一句话说,ref是有进有出,而out是只出不进。
代码示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Parameter
{
class Program
{
static void Main(string[] args)
{
Test1 test1 = new Test1();
test1.MainClaee1();
Test2 test2 = new Test2();
test2.MainClass2();
Test3 test3 = new Test3();
test3.MainClaee3();
Test4 test4 = new Test4();
test4.MainClass4();
Console.ReadKey();
}
}
#region 值类型参数的按值传递
class Test1
{
public void MainClaee1()
{
int a = 1;
Change(a);
Console.WriteLine(a + "\n");
}
private static void Change(int b)
{
b = b + 2;
Console.WriteLine(b);
}
}
#endregion 值类型参数的按值传递
#region 引用类型参数的按值传递
class Test2
{
public void MainClass2()
{
ArgsByRef abr = new ArgsByRef();
Change(abr);
Console.WriteLine(abr.i + "\n");
}
private static void Change(ArgsByRef abr)
{
abr.i = 20;
Console.WriteLine(abr.i);
}
}
class ArgsByRef
{
public int i = 10;
}
#endregion 引用类型参数的按值传递
#region string类型作为引用类型的特殊性
class Test3
{
private int i = 10;
public void MainClaee3()
{
string str = "Old Str";
Test3 test3 = new Test3();
Change(test3);
ChangeStr(str);
Console.WriteLine(str);
Console.WriteLine(i + "\n");
}
private static void Change(Test3 t)
{
t = new Test3();
t.i = 20;
}
private static void ChangeStr(string str)
{
str = "New Str";
Console.WriteLine(str);
}
}
#endregion string类型作为引用类型的特殊性
#region 按引用传递 ref和out
class Test4
{
public void MainClass4()
{
int i = 10;//int i; ref使用前,必须先赋值
Console.WriteLine("使用ref传值:");
ChangeByRef(ref i);
Console.WriteLine(i);
int i2;
Console.WriteLine("使用out传值:");
ChangeByOut(out i2);
Console.WriteLine(i2);
}
private static void ChangeByRef(ref int i)
{
i = 20;
Console.WriteLine(i);
}
private static void ChangeByOut(out int i)
{
i = 30;
Console.WriteLine(i);
}
}
#endregion 按引用传递 ref和out
}