今天我们来研究下用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方法的第一个参数既是传入的第一个参数,所以在使用时要注意.要写一个循环,和我们之前的代码有所不同的两个地方就是取数组里的元素和取数组的长度.
其中,取数组里的元素使用如下的指令流程:
- 首先,加载数组指针到堆栈: methodIL.Emit(OpCodes.Ldarg_0);
- 然后,加载现在要加载的元素在数组中的索引:methodIL.Emit(OpCodes.Ldloc_1);
- 最后,获得数组中的元素:methodIL.Emit(OpCodes.Ldelem_I4);这里的Ldelem_I4表示加载的数组元素为Int32类型。
取数组的长度使用下面这样的指令流程:
- 首先,加载数组指针到堆栈: methodIL.Emit(OpCodes.Ldarg_0);
- 然后,加载数组的长度:methodIL.Emit(OpCodes.Ldlen);
- 最后,由于加载的数组长度为无符号整数,所以还需要转换成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语句,我想肯定是我哪里的分析有误,忘有人能够给我指出,不甚感激!