今天我們來研究下用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語句,我想肯定是我哪裡的分析有誤,忘有人能夠 給我指出,不甚感激!
本文配套源碼