C#中ref和out的文章有很多,這篇文章主要從IL上分析out參數的本質。
目錄
- out = ref + ParameterAttributes.Out
- 反射調用ref和out方法
- Emit構建out或ref參數
返回目錄
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方法,你會看到:
一切正確!