您的位置:首页 > Web前端

解析C#中reference与out关键字

2018-01-14 19:05 477 查看
C#中数据类型分为两种:值类型和引用类型,值类型为:基本类型(枚举、数值、布尔)、String、Struct类型,而其他均为引用类型。在引用类型中,reference标注是否对该类型无影响呢?

测试程序如下:

class A {
public A()
{
Console.WriteLine("A::A()");
}
public string val;
};
static void test(ref A name)
{
name.val = "ref helloA";
}
static void test(A name)
{
name.val = "helloA";
}
对于这样的两个方法,生成的IL是不一样的,但执行结果是一样的:



.method private hidebysig static void  test(class TestRef.A& name) cil managed
{
// 代码大小       14 (0xe)
.maxstack  8//栈大小
IL_0000:  nop
IL_0001:  ldarg.0//加载name指针
IL_0002:  ldind.ref //以引用方式加载name的值,ind:indirect,ref:reference
IL_0003:  ldstr      "ref helloA"//加载string对象
IL_0008:  stfld      string TestRef.A::val//将val赋值为string对象
IL_000d:  ret
} // end of method Program::test
.method private hidebysig static void  test(class TestRef.A name) cil managed
{
// 代码大小       13 (0xd)
.maxstack  8
IL_0000:  nop
IL_0001:  ldarg.0
IL_0002:  ldstr      "helloA"
IL_0007:  stfld      string TestRef.A::val
IL_000c:  ret
} // end of method Program::test

引用参数ref在生成代码是多了一个受限指针类型,也就是C++中的引用类型。所有对象存放在堆中,如不加引用传递的也是name的地址(*name),修改name成员均有效,而引用类型传递的是name地址的地址(也就是指针的指针**name),这样就可以修改name地址,这也就说明了ref与out参数的区别。因所有对象按地址传递故也不存在构造函数生成临时对象的场景。

out参数的含义为该参数未进行初始化,需重新初始化,引用类型按引用传递该关键字含义如何?
static void test( A name)
{
name = null;
}
static void test(out A name)
{
name = null;
}
对于这样的两个方法生成的IL如下:



但两个IL执行结果完全不同,out方法产生了空值异常。
.method private hidebysig static void  test([out] class TestRef.A& name) cil managed
{
// 代码大小       5 (0x5)
.maxstack  8
IL_0000:  nop
IL_0001:  ldarg.0//ld代表load
IL_0002:  ldnull
IL_0003:  stind.ref
IL_0004:  ret
} // end of method Program::test
.method private hidebysig static void  test(class TestRef.A name) cil managed
{
// 代码大小       5 (0x5)
.maxstack  8
IL_0000:  nop
IL_0001:  ldnull
IL_0002:  starg.s    name//将值存入name字段,也就是name赋值为null
IL_0004:  ret
} // end of method Program::test

从中间代码中可以看到ref,out关键字涉及间接寻址(就是指令stdind.ref),而引用类型传递的name地址,当然就无法修改name地址。而ref、out关键字在生成函数签名时均为多加了一个取地址符&,这样就说明了如果一个方法仅在参数上只有ref/out区别是无法被编译通过的。

形参不加ref或out如下调用时,对象name是不会修改的:
static void test(A name)
{
name = new A();
name.val = "new A";
}生成的IL如下,可以看出涉及对象name根本不会间接寻址:
.method private hidebysig static void test(class TestRef.A name) cil managed
{
// 代码大小 20 (0x14)
.maxstack 8
IL_0000: nop
IL_0001: newobj instance void TestRef.A::.ctor()
IL_0006: starg.s name////将值存入name字段,也就是name赋值为newobj
IL_0008: ldarg.0
IL_0009: ldstr "new A"
IL_000e: stfld string TestRef.A::val
IL_0013: ret
} // end of method Program::test值类型传递参数时,如不加ref/out关键字,则完全无法修改对象内部成员,不同的是作为值类型对象struct,无法定义默认构造函数和析构函数,按照Essential C#中的说法,原因为struct类型初始化直接清零,允许默认构造函数将会造成CLR不一致的行为,而由于按值传递,加入析构函数就意味这该对象资源释放,导致按值传递过程中大量的对象资源被意外释放,这样就会造成对象的访问异常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息