[C#]浅析ref、out参数

带ref的艺术里多了一条指令“ldind.ref”,关于那条指令MSDN的解释是那般的:

其权且候的办法达成恐怕是如此的:

图片 1

图片 2

图片 3

以上海市总计起来正是一句话:
按值传递参数的艺术永远不容许改变方法外的变量,要求改变方法外的变量就必须按引用传递参数。

this 关键字引用类的当前实例

红字部分代码会报出如注释所述的荒谬。两段代码唯一的差距在于前者是struct(值类型)而后者是class(引用类型)。
前面已经说过,ref标记的参数在措施内部的修改会影响到艺术外的变量值,所以用ref标记this传入方法或者引致this的值被更改。
幽默的是,为何struct里的this允许被改成,而class里的this分歧意被更改吗?

说了这么久引用,再来看一看同样能够用来写Swap的指针。
很醒目,ref参数和指针参数的类型是区别的,所以这么写是足以因此编写翻译的:

 转载:http://www.cnblogs.com/vd630/p/4601919.html\#top

按值传参传的正是值咯,按引用传参传的正是引用咯,这么简单的题材还有啥可商量的吗。
而是想一想,值类型变量和引用类型变量组合上按值传参和按引用传参,一共两种情状,某个境况下“值”和“引用”可能指的是同二个东西。

class Class
{
    void Method(ref Class @class) { }

    void Test()
    {
        // 无法将“<this>”作为 ref 或 out 参数传递,因为它是只读的
        Method(ref this);
    }
}

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
struct Struct
{
    void Method(ref Struct @struct) { }

    public void Test()
    {
        Method(ref this);
    }
}

当今的IL是那般的:

那二回两方的异样就更大了。
无ref版本做的事很简短,new了1个Class对象然后径直赋给了@class。
只是有ref版本则是先取了ref引用留着待会用,再new了Class,然后才把这些Class对象赋给ref引用指向的地方。
在来看望调用方会有怎样分歧:

PS:不是通过传参的点子传入的变量当然是足以被改变的,本文不对那种气象做钻探。

能够观望,不管方法内部对“值”和“B引用”作什么修改,五个变量包蕴的消息是不会有任何变化的。
而是也能够见见,方法内部是能够通过“B引用”对“引用类型对象”进行改动的,那就应运而生了前文所说的爆发在List上的风貌。
而按引用传递时如同那样:

三 、从IL来看反差

地点那段代码是足以经过编写翻译的,然而假若像下边这样写就不行了:

往下的始末和ref其实没啥太大关系了,可是涉及到值和引用,所以照旧持续写吧:D

一旦对值类型的this实行赋值,那么“值”被涂改,“当前实例”还是是本来实例对象,只是内容变了。
而倘使对引用类型的this进行复制,那么“B引用”被改动,出现了看似于这个图的情状,未来的“当前实例”已经不是原来的实例对象了,this关键字的意思就不再鲜明。所以引用类型中的this应该是只读的,确定保障“this”就是指向的“那么些”对象。

能够观看,那几个时候方法内部是能够通过“引用”和“A引用”直接改动变量的新闻的,甚至只怕产生那样的动静:

将对象引用作为 O(对象引用)类型间接加载到计算堆栈上。

class Class
{
    void Method(Class @class)
    {
        @class = new Class();
    }
    void Method(ref Class @class)
    {
        @class = new Class();
    }
}

五、this和引用

按值传递时就像那样:

世家都领悟,按值传递的参数在点子内部不管怎么改变,方法外的变量都不会受到震慑,那从学C语言时候就听老师说过的了。
在C语言里想要写3个Swap方法该如何是好?用指针咯。
那么在C#里该怎么办?固然也能够用指针,不过更常见也更安全的做法就是用ref咯。

unsafe struct Struct
{
    void Method(ref Struct @struct) { }
    void Method(Struct* @struct) { }
}

四 、引用与指针

.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

ref版本是用了取地址运算符(&)来标记,而指针版本用的是直接寻址运算符(*),含义也都很强烈,前者传入的是二个变量的位置(即引用),后者传入的是3个指针类型。
更有意思的事情是那般的:

所谓的按值传递,便是传递的“二”;按引用传递,正是传递的“一”。
也等于说,在按值传递一个引用类型的时候,传递的值的内容是一个引用。

三个方法体的IL是一模一样的!能够测算引用的真相到底是如何了啊~?

差别很清楚,前者从一些变量表取“值”,后者从一些变量表取“引用”。

先简单地从变量说起吧,1个变量总是和内部存款和储蓄器中的二个目的相关联。
对于值类型的变量,能够认为它总是包蕴多个音信,一是援引,二是指标的值。前者就是指向后者的引用。
对于引用类型的变量,能够认为它也含有七个消息,一是引用,二是另2个引用。前者依然是指向后者的引用,而后者则针对堆中的对象。

此处的“当前实例”指的是内部存款和储蓄器中的对象,也正是下图中的“值”或“引用类型对象”:

② 、参数传递的是何等

为了阅读方便,小编把原来的默许无参构造函数去掉了。
可以看出八个法子的IL仅仅唯有3个&符号的出入,那一个标志的出入也是四个点子能够同名的案由,因为它们的参数类型是不一样等的。out和ref参数的门类则是同一的。
现行反革命给代码里加一点情节,让出入变得更明了有个别:

class Class
{
    void Method(Class @class) { }
    void Method(ref Class @class) { }
    // void Method(out Class @class) { }
}

接下去看一看IL是怎么对待按值恐怕按引用传递的参数。比如这一段C#代码:

按引用传递的参数算是C#与触目皆是任何语言相比的一大特点,想要深入明白这一定义应该说不是一件不难的事,再把值类型和引用类型给参杂进来的话就变得更为令人头晕了。
时不时看看有人把按引用传递和引用类型混为一谈,让本身有点不吐相当的慢。再加上前两日境遇的八个诙谐的标题,让自个儿进一步觉得应该整理整理有关ref和out的内容了。

以此妙不可言的题材是前二日才意识到的,在此以前根本不曾写过类似那样的代码:

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
void SampleMethod(ref object obj)
{
    //.....
    obj = new object();
    //.....
}

ref和out用起来依然非凡简单的,便是在普通的按值传递的参数前加个ref或然out就行,方法定义和调用的时候都得加。
ref和out都以代表按引用传递,CL凯雷德也截然不区分ref依旧out,所以下文就径直以ref为例来举办认证。

这一段代码是足以健康通过编写翻译的,但是打消注释就万分了,原因前面也涉嫌了,IL是不区分ref和out的。
也多亏因为这一种重载的大概,所以在调用方也必须写明ref或out,不然编写翻译器无法区分调用的是哪一个重载版本。
Class类的IL是这么的:

壹 、什么是按引用传递

图片 4

.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

大致情状类似于如此:

.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

MSDN对“this”关键字的解释是那般的:

说到此地,有有个别需求显然,按值传递的参数到底会不会被更改。
万一传的是int参数,方法外的变量肯定是完完全全不变的咯,但是如若传的是个List呢?方法内部对这几个List的全部增加和删除改都会反映到方式外头,方法外查一下Count就能看出来了是吧。
那就是说传List的这些情形,也象征了具有引用类型参数的意况,方法外的变量到底变没变?
永不听信有些论调说怎样“引用类型就是传引用”,不用ref的状态下引用类型参数依然传的是“值”,所以艺术外的变量还是是不变的。

图片 5

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

那七个点子的IL分外有趣:

class Class
{
    int i;

    void Method(Class @class)
    {
        @class.i = 1;
    }
    void Method(ref Class @class)
    {
        @class.i = 1;
    }
}