从IL和反射Emit更彻底地理解out参数

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值