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方法,你会看到:
一切正确!