现在的位置: 首页 > 综合 > 正文

.NET(C#):从IL和反射Emit更彻底地理解out参数

2012年01月07日 ⁄ 综合 ⁄ 共 2433字 ⁄ 字号 评论关闭

C#中ref和out的文章有很多,这篇文章主要从IL上分析out参数的本质。

 

 

 

返回目录

out = ref + ParameterAttributes.Out

看两个out和ref的方法。

static void test_ref(ref int i)

{ }

 

static void test_out(out int i)

{ i = default(int); }

 

IL:

.method private hidebysig static void test_ref(int32& i) cil managed

 

.method private hidebysig static void test_out([out] int32& i) cil managed

 

可以看到,out参数是ref参数(都是传的参数的地址,可以理解成C/C++中的指针),只不过加了一个[out]。

这个也可以从反射中的ParameterInfo类的Attributes属性验证。

public class Program

{

    static void Main(string[] args)

    {

        var refFunc = typeof(Program).GetMethod("test_ref");

        var outFunc = typeof(Program).GetMethod("test_out");

 

        foreach(var pa in refFunc.GetParameters())

            Console.WriteLine(pa.Attributes);

 

        foreach(var pa in outFunc.GetParameters())

            Console.WriteLine(pa.Attributes);

    }

 

    public static void test_ref(ref int i)

    { }

 

    public static void test_out(out int i)

    { i = default(int); }

 

}

 

输出:

None

Out

 

ref参数的ParameterInfo属性是None,数值上等于0,而out参数是Out枚举值。

 

返回目录

反射调用ref和out方法

由于out就是ref,所以反射调用没有区别,反射调用需要一个object数组作参数,编译器肯定不允许你把一个未初始化的变量放在数组里,即便是它要当做out参数。同时只要数组被建立,里面的元素默认是null的,不管你是否吧null替换成其他值,最终ref或out参数都会修改相应值的。

static void Main(string[] args)

{

    var m = typeof(Program).GetMethod("doo");

    var arg = new object[] { null, null };

    m.Invoke(null, arg);

    Console.WriteLine("{0} {1}", arg[0], arg[1]);

}

public static void doo(ref int a, out int b)

{

    a = b = 1;

}

 

输出两个1。值都被正确修改了。

 

 

返回目录

Emit构建out或ref参数

先在用反射Emit的TypeBuilder创建一个和上例doo一样的动态方法!

 

结合上面的知识,在TypeBuilder的DefineMethod方法上,两个参数都是int的引用参数(typeof(int).MakeByRefType()),要想把第二个参数设置成out,则需要调用MethodBuilder.DefineParameter方法,设置ParameterAttributes.Out在指定参数上才可以!

 

代码:

static void CreateMethod(TypeBuilder tb)

{

    var mbuilder = tb.DefineMethod("doo",

        MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static,

         CallingConventions.Standard,

         null,

         new Type[] { typeof(int).MakeByRefType(), typeof(int).MakeByRefType() });

    //ref和out都是typeof(int).MakeByRefType()

 

    //设置ref参数名称为a

    mbuilder.DefineParameter(1, ParameterAttributes.None, "a");

    //设置out参数名称为b

    //并且为参数b设置ParameterAttributes.Out(否则它和ref参数一样)

    mbuilder.DefineParameter(2, ParameterAttributes.Out, "b");

 

    var ilgen = mbuilder.GetILGenerator();

    //a=b=1

    ilgen.Emit(OpCodes.Ldarg_0);

    ilgen.Emit(OpCodes.Ldc_I4_1);

    ilgen.Emit(OpCodes.Stind_I4);

    ilgen.Emit(OpCodes.Ldarg_1);

    ilgen.Emit(OpCodes.Ldc_I4_1);

    ilgen.Emit(OpCodes.Stind_I4);

 

    ilgen.Emit(OpCodes.Ret);

}

 

OK,用Reflector打开我们用Emit生成的程序集的这个doo方法,你会看到:

image

 

 

一切正确!

Open-mouthed smile

抱歉!评论已关闭.