今天我們來研究下用IL書寫For循環和Foreach循環,在書中一直看到說使用 Foreach循環比普通的For循環來的好,這次正好趁機來看看他們的IL代碼有何不同 .按照慣例,我們先給出要實現的類的C#代碼,如下:
class Iterator { public int ForMethod(int[] ints) { int sum = 0; for (int i = 0; i < ints.Length; i++) { sum += ints[i]; } return sum; } public int ForeachMethod(int[] ints) { int sum = 0; foreach (int i in ints) { sum += i; } return sum; } }
然後,我們先來實現其中的ForMethod,相信大家已經等不及了,那麼我先給出實 現的IL代碼,然後針對其中的關鍵部分進行講解,代碼如下:
MakeForMethod
/// <summary> /// 生成For循環 /// </summary> /// <param name="typeBuilder"></param> static void MakeForMethod(TypeBuilder typeBuilder) { //定義一個傳入參數為Int32[],返回值為Int32的方法 MethodBuilder methodBuilder = typeBuilder.DefineMethod ("ForMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(Int32), new Type[] { typeof(Int32[]) }); ILGenerator methodIL = methodBuilder.GetILGenerator(); //用來保存求和結果的局部變量 LocalBuilder sum = methodIL.DeclareLocal(typeof(Int32)); //循環中使用的局部變量 LocalBuilder i = methodIL.DeclareLocal(typeof(Int32)); Label compareLabel = methodIL.DefineLabel(); Label enterLoopLabel = methodIL.DefineLabel(); //int sum = 0; methodIL.Emit(OpCodes.Ldc_I4_0); methodIL.Emit(OpCodes.Stloc_0); //int i = 0 methodIL.Emit(OpCodes.Ldc_I4_0); methodIL.Emit(OpCodes.Stloc_1); methodIL.Emit(OpCodes.Br, compareLabel); //定義一個標簽,表示從下面開始進入循環體 methodIL.MarkLabel(enterLoopLabel); //sum += ints[i]; //其中Ldelem_I4用來加載一個數組中的Int32類型的元素 methodIL.Emit(OpCodes.Ldloc_0); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldloc_1); methodIL.Emit(OpCodes.Ldelem_I4); methodIL.Emit(OpCodes.Add); methodIL.Emit(OpCodes.Stloc_0); //i++ methodIL.Emit(OpCodes.Ldloc_1); methodIL.Emit(OpCodes.Ldc_I4_1); methodIL.Emit(OpCodes.Add); methodIL.Emit(OpCodes.Stloc_1); //定義一個標簽,表示從下面開始進入循環的比較 methodIL.MarkLabel(compareLabel); //i < ints.Length methodIL.Emit(OpCodes.Ldloc_1); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldlen); methodIL.Emit(OpCodes.Conv_I4); methodIL.Emit(OpCodes.Clt); methodIL.Emit(OpCodes.Brtrue_S, enterLoopLabel); //return sum; methodIL.Emit(OpCodes.Ldloc_0); methodIL.Emit(OpCodes.Ret); }
為了在測試時方便點,這裡我為方法加上了Static標簽,要注意的是,非靜態方 法的第一參數是this指針,而Static方法的第一個參數既是傳入的第一個參數,所 以在使用時要注意.要寫一個循環,和我們之前的代碼有所不同的兩個地方就是取 數組裡的元素和取數組的長度.
其中,取數組裡的元素使用如下的指令流程:
l 首先,加載數組指針到堆棧: methodIL.Emit(OpCodes.Ldarg_0);
l 然後,加載現在要加載的元素在數組中的索引:methodIL.Emit (OpCodes.Ldloc_1);
l 最後,獲得數組中的元素:methodIL.Emit(OpCodes.Ldelem_I4);這裡的 Ldelem_I4表示加載的數組元素為Int32類型。
取數組的長度使用下面這樣的指令流程:
l 首先,加載數組指針到堆棧: methodIL.Emit(OpCodes.Ldarg_0);
l 然後,加載數組的長度:methodIL.Emit(OpCodes.Ldlen);
l 最後,由於加載的數組長度為無符號整數,所以還需要轉換成Int32類型 來使用:methodIL.Emit(OpCodes.Conv_I4);
接下來,我們給出ForeachMethod的IL實現,代碼如下:
MakeForeachMethod
/// <summary> /// 生成Foreach循環 /// </summary> /// <param name="typeBuilder"></param> static void MakeForeachMethod(TypeBuilder typeBuilder) { //定義一個傳入參數為Int32[],返回值為Int32的方法 MethodBuilder methodBuilder = typeBuilder.DefineMethod ("ForeachMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(Int32), new Type[] { typeof(Int32[]) }); ILGenerator methodIL = methodBuilder.GetILGenerator(); //用來保存求和結果的局部變量 LocalBuilder sum = methodIL.DeclareLocal(typeof(Int32)); //foreach 中的 int i LocalBuilder i = methodIL.DeclareLocal(typeof(Int32)); //用來保存傳入的數組 LocalBuilder ints = methodIL.DeclareLocal(typeof(Int32[])); //數組循環用臨時變量 LocalBuilder index = methodIL.DeclareLocal(typeof(Int32)); Label compareLabel = methodIL.DefineLabel(); Label enterLoopLabel = methodIL.DefineLabel(); //int sum = 0; methodIL.Emit(OpCodes.Ldc_I4_0); methodIL.Emit(OpCodes.Stloc_0); //ints = ints methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Stloc_2); //int index = 0 methodIL.Emit(OpCodes.Ldc_I4_0); methodIL.Emit(OpCodes.Stloc_3); methodIL.Emit(OpCodes.Br, compareLabel); //定義一個標簽,表示從下面開始進入循環體 methodIL.MarkLabel(enterLoopLabel); //其中Ldelem_I4用來加載一個數組中的Int32類型的元素 //加載 i = ints[index] methodIL.Emit(OpCodes.Ldloc_2); methodIL.Emit(OpCodes.Ldloc_3); methodIL.Emit(OpCodes.Ldelem_I4); methodIL.Emit(OpCodes.Stloc_1); //sum += i; methodIL.Emit(OpCodes.Ldloc_0); methodIL.Emit(OpCodes.Ldloc_1); methodIL.Emit(OpCodes.Add); methodIL.Emit(OpCodes.Stloc_0); //index++ methodIL.Emit(OpCodes.Ldloc_3); methodIL.Emit(OpCodes.Ldc_I4_1); methodIL.Emit(OpCodes.Add); methodIL.Emit(OpCodes.Stloc_3); //定義一個標簽,表示從下面開始進入循環的比較 methodIL.MarkLabel(compareLabel); //index < ints.Length methodIL.Emit(OpCodes.Ldloc_3); methodIL.Emit(OpCodes.Ldloc_2); methodIL.Emit(OpCodes.Ldlen); methodIL.Emit(OpCodes.Conv_I4); methodIL.Emit(OpCodes.Clt); methodIL.Emit(OpCodes.Brtrue_S, enterLoopLabel); //return sum; methodIL.Emit(OpCodes.Ldloc_0); methodIL.Emit(OpCodes.Ret); }
其中用紅色字體標出的是它和ForMethod的主要不同之處,首先,它用一個局 部變量保存了整個數組,並用它替換了所有原先直接使用數組的地方;最後,它 把sum += ints[i];的操作分解成為i = ints[index]和sum += i兩個步驟。這兩 個不同之處也是我分析了自動生成的IL代碼之後才得出的,具體為什麼還希望有 人能夠給我指點,因為都是說Foreach循環所生成的代碼是最優的,但是在這裡看 來卻比For循環多生成了一些IL語句,我想肯定是我哪裡的分析有誤,忘有人能夠 給我指出,不甚感激!
本文配套源碼