在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
转载:http://www.cnblogs.com/vd630/p/4601919.html#top 按引用传递的参数算是C#与很多其他语言相比的一大特色,想要深入理解这一概念应该说不是一件容易的事,再把值类型和引用类型给参杂进来的话就变得更加让人头晕了。 一、什么是按引用传递 ref和out用起来还是非常简单的,就是在普通的按值传递的参数前加个ref或者out就行,方法定义和调用的时候都得加。 大家都知道,按值传递的参数在方法内部不管怎么改变,方法外的变量都不会受到影响,这从学C语言时候就听老师说过的了。 说到这里,有一点需要明确,按值传递的参数到底会不会被改变。 以上总结起来就是一句话: PS:不是通过传参的方式传入的变量当然是可以被改变的,本文不对这种情况做讨论。 二、参数传递的是什么 按值传参传的就是值咯,按引用传参传的就是引用咯,这么简单的问题还有啥可讨论的呢。 先简单地从变量说起吧,一个变量总是和内存中的一个对象相关联。 所谓的按值传递,就是传递的“二”;按引用传递,就是传递的“一”。 大概情况类似于这样: 按值传递时就像是这样: 可以看到,不管方法内部对“值”和“B引用”作什么修改,两个变量包含的信息是不会有任何变化的。 可以看到,这个时候方法内部是可以通过“引用”和“A引用”直接修改变量的信息的,甚至可能发生这样的情况: 这个时候的方法实现可能是这样的: void SampleMethod(ref object obj) { //..... obj = new object(); //..... } 三、从IL来看差异 接下来看一看IL是怎么对待按值或者按引用传递的参数。比如这一段C#代码: class Class { void Method(Class @class) { } void Method(ref Class @class) { } // void Method(out Class @class) { } } 这一段代码是可以正常通过编译的,但是取消注释就不行了,原因前面也提到了,IL是不区分ref和out的。
.class private auto ansi beforefieldinit CsConsole.Class extends [mscorlib]System.Object { // Methods .method private hidebysig static void Method ( class CsConsole.Class 'class' ) cil managed { // Method begins at RVA 0x20b4 // Code size 1 (0x1) .maxstack 8 IL_0000: ret } // end of method Class::Method .method private hidebysig static void Method ( class CsConsole.Class& 'class' ) cil managed { // Method begins at RVA 0x20b6 // Code size 1 (0x1) .maxstack 8 IL_0000: ret } // end of method Class::Method } // end of class CsConsole.Class 为了阅读方便,我把原有的默认无参构造函数去掉了。
class Class { int i; void Method(Class @class) { @class.i = 1; } void Method(ref Class @class) { @class.i = 1; } } 现在的IL是这样的: .class private auto ansi beforefieldinit CsConsole.Class extends [mscorlib]System.Object { // Fields .field private int32 i // Methods .method private hidebysig instance void Method ( class CsConsole.Class 'class' ) cil managed { // Method begins at RVA 0x20b4 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.1 IL_0001: ldc.i4.1 IL_0002: stfld int32 CsConsole.Class::i IL_0007: ret } // end of method Class::Method .method private hidebysig instance void Method ( class CsConsole.Class& 'class' ) cil managed { // Method begins at RVA 0x20bd // Code size 9 (0x9) .maxstack 8 IL_0000: ldarg.1 IL_0001: ldind.ref IL_0002: ldc.i4.1 IL_0003: 带ref的方法里多了一条指令“ldind.ref”,关于这条指令MSDN的解释是这样的:stfld int32 CsConsole.Class::i IL_0008: ret } // end of method Class::Method } // end of class CsConsole.Class 带ref的方法里多了一条指令“ldind.ref”,关于这条指令MSDN的解释是这样的: 将对象引用作为 O(对象引用)类型间接加载到计算堆栈上。 class Class { void Method(Class @class) { @class = new Class(); } void Method(ref Class @class) { @class = new Class(); } } IL是这样的: .class private auto ansi beforefieldinit CsConsole.Class extends [mscorlib]System.Object { // Methods .method private hidebysig instance void Method ( class CsConsole.Class 'class' ) cil managed { // Method begins at RVA 0x20b4 // Code size 8 (0x8) .maxstack 8 IL_0000: newobj instance void CsConsole.Class::.ctor() IL_0005: starg.s 'class' IL_0007: ret } // end of method Class::Method .method private hidebysig instance void Method ( class CsConsole.Class& 'class' ) cil managed { // Method begins at RVA 0x20bd // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.1 IL_0001: newobj instance void CsConsole.Class::.ctor() IL_0006: stind.ref IL_0007: ret } // end of method Class::Method } // end of class CsConsole.Class 这一次两方的差别就更大了。
class Class { void Method(Class @class) { } void Method(ref Class @class) { } void Caller() { Class @class = new Class(); Method(@class); Method(ref @class); } } .method private hidebysig instance void Caller () cil managed { // Method begins at RVA 0x20b8 // Code size 22 (0x16) .maxstack 2 .locals init ( [0] class CsConsole.Class 'class' ) IL_0000: newobj instance void CsConsole.Class::.ctor() IL_0005: stloc.0 IL_0006: ldarg.0 IL_0007: ldloc.0 IL_0008: call instance void CsConsole.Class::Method(class CsConsole.Class) IL_000d: ldarg.0 IL_000e: ldloca.s 'class' IL_0010: cal
差别很清晰,前者从局部变量表取“值”,后者从局部变量表取“引用”。
四、引用与指针 说了这么久引用,再来看一看同样可以用来写Swap的指针。
unsafe struct Struct { void Method(ref Struct @struct) { } void Method(Struct* @struct) { } } 这两个方法的IL非常有意思: .class private sequential ansi sealed beforefieldinit CsConsole.Struct extends [mscorlib]System.ValueType { .pack 0 .size 1 // Methods .method private hidebysig instance void Method ( valuetype CsConsole.Struct& 'struct' ) cil managed { // Method begins at RVA 0x2050 // Code size 1 (0x1) .maxstack 8 IL_0000: ret } // end of method Struct::Method .method private hidebysig instance void Method ( valuetype CsConsole.Struct* 'struct' ) cil managed { // Method begins at RVA 0x2052 // Code size 1 (0x1) .maxstack 8 IL_0000: ret } // end of method Struct::Method } // end of class CsConsole.Struct ref版本是用了取地址运算符(&)来标记,而指针版本用的是间接寻址运算符(*),含义也都很明显,前者传入的是一个变量的地址(即引用),后者传入的是一个指针类型。
unsafe struct Struct { void Method(ref Struct @struct) { @struct = default(Struct); } void Method(Struct* @struct) { *@struct = default(Struct); } } .class private sequential ansi sealed beforefieldinit CsConsole.Struct extends [mscorlib]System.ValueType { .pack 0 .size 1 // Methods .method private hidebysig instance void Method ( valuetype CsConsole.Struct& 'struct' ) cil managed { // Method begins at RVA 0x2050 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.1 IL_0001: initobj CsConsole.Struct IL_0007: ret } // end of method Struct::Method .method private hidebysig instance void Method ( valuetype CsConsole.Struct* 'struct' ) cil managed { // Method begins at RVA 0x2059 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.1 IL_0001: initobj CsConsole.Struct IL_0007: ret } // end of method Struct::Method } // end of class CsConsole.Struct 两个方法体的IL是一模一样的!可以想见引用的本质到底是什么了吧~? 五、this和引用 这个有趣的问题是前两天才意识到的,以前从来没有写过类似这样的代码: struct Struct { void Method(ref Struct @struct) { } public void Test() { Method(ref this); } } 上面这段代码是可以通过编译的,但是如果像下面这样写就不行了: class Class { void Method(ref Class @class) { } void Test() { // 无法将“<this>”作为 ref 或 out 参数传递,因为它是只读的 Method(ref this); } } 红字部分代码会报出如注释所述的错误。两段代码唯一的差别在于前者是struct(值类型)而后者是class(引用类型)。
往下的内容和ref其实没啥太大关系了,但是涉及到值和引用,所以还是继续写吧:D MSDN对“this”关键字的解释是这样的: this 关键字引用类的当前实例 这里的“当前实例”指的是内存中的对象,也就是下图中的“值”或“引用类型对象”: 如果对值类型的this进行赋值,那么“值”被修改,“当前实例”仍然是原来实例对象,只是内容变了。
|
请发表评论