从IL认识关键字(一)
背景
网上流传“没有用.NET Reflector反编译并阅读过代码的程序员不是专业的.NET程序员” 。虽然是夸张手法,但是.NET Reflector确实是.Net程序员必不可少的一个工具。但是最近7.0后版本开始收费,功能是强大了,可以直接在VS上反编译看源代码。还有更多的功能没深入研究,但是再多的功能也抵不过编译成.net代码和IL。
关键字
第一篇先来研究foreach这个关键字,可能大家都很熟悉,这个关键字的原理。但是既然要整理研究,就系统的整理一遍。若是已经熟悉这个关键字,可以返回。
集合遍历
C# 代码, 准备一个Student类,里面只有ID,Name属性
IList<Student> students = new List<Student>(); foreach (Student stu in students) { Console.WriteLine(stu.Name); }
由于IL代码较多,只贴上部分重要代码
L_0009: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<class Foreach.Student>::GetEnumerator()
L_000e: stloc.2
L_000f: br.s L_0026
L_0011: ldloc.2
L_0012: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<class Foreach.Student>::get_Current()
L_0017: stloc.1
L_0018: nop
L_0019: ldloc.1
L_001a: callvirt instance string Foreach.Student::get_Name()
L_001f: call void [mscorlib]System.Console::WriteLine(string)
L_0024: nop
L_0025: nop
L_0026: ldloc.2
L_0027: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_002c: stloc.3
L_002d: ldloc.3
L_002e: brtrue.s L_0011
L_0030: leave.s L_0042
L_0032: ldloc.2
L_0033: ldnull
L_0034: ceq
L_0036: stloc.3
L_0037: ldloc.3
L_0038: brtrue.s L_0041
L_003a: ldloc.2
L_003b: callvirt instance void [mscorlib]System.IDisposable::Dispose()
.try L_000f to L_0032 finallyhandler L_0032 to L_0042
从IL代码( L_0009 -- L_0030) 看到foreach其实就是一个Enumerator枚举器的遍历。
翻译后C#代码
IList<Student> students = new List<Student>(); IEnumerator<Student> enumerator = students.GetEnumerator(); try { while (enumerator.MoveNext()) { Student stu = enumerator.Current as Student; Console.WriteLine(stu.Name); } } finally { enumerator.Dispose(); }
翻译后代码已与重新反编译成IL与foreach的IL几乎一致
(注:foreach生成的IL,不仅foreach,还有其他关键字。会比直接写代码多了一些nop指令,网上查阅查阅这个指令的意义是 Do nothing.既然是Do nothing即不影响程序运行,我认为可以忽略)
验证代码
虽然说可以直接将IL代码翻译过来,但是我们还要保持一颗怀疑的态度,包括怀疑自己。下面是验证代码:
1)自己实现IEnumerator,IEnumerable类,输出Current属性与MoveNext()
下面准备一个Students类实现IEnumerator,IEnumerable接口
关于IEnumerator和IEnumerable的区别,网上也有很多解释,我的理解大约就是
IEnumerable:实现IEnumerable才能实现枚举
IEnumerator:实现一个枚举器
class Students : IEnumerator<Student>, IEnumerable<Student> { private static List<Student> list = new List<Student>(); static Students() { list.Add(new Student() { ID = 1, Name = "Jack" }); list.Add(new Student() { ID = 2, Name = "Rose" }); } private int index = 0; private Student current; #region IEnumerator接口 public Student Current { get { Console.WriteLine("----------Current----------"); return this.current; } } object System.Collections.IEnumerator.Current { get { return this.current; } } public bool MoveNext() { Console.WriteLine("----------MoveNext----------"); if (this.index < list.Count) { this.current = list[this.index]; this.index++; return true; } return false; } public void Reset() { this.index = 0; this.current = null; } public void Dispose() { } #endregion #region IEnumerable接口 public IEnumerator<Student> GetEnumerator() { return this; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this; } #endregion }
运行代码截图如下,和我们之前预想一致
数组遍历
经过园友提醒,foreach漏了还有数组的遍历,数组遍历也是我们平常最常用的。在这里加上数组的遍历。下面是简单一个例子
public void EeachArray() { foreach(int item in nums) { } }
对应的IL如下:
.locals init ( [0] int32 num, [1] int32[] numArray, [2] int32 num2, [3] bool flag) L_0000: nop L_0001: nop L_0002: ldarg.0 L_0003: ldfld int32[] Test.Program::nums L_0008: stloc.1 L_0009: ldc.i4.0 L_000a: stloc.2 L_000b: br.s L_0017 L_000d: ldloc.1 L_000e: ldloc.2 L_000f: ldelem.i4 L_0010: stloc.0 L_0011: nop L_0012: nop L_0013: ldloc.2 L_0014: ldc.i4.1 L_0015: add L_0016: stloc.2 L_0017: ldloc.2 L_0018: ldloc.1 L_0019: ldlen L_001a: conv.i4 L_001b: clt L_001d: stloc.3 L_001e: ldloc.3 L_001f: brtrue.s L_000d L_0021: ret
这里分为三个部分
- (L_0000 -- L_000b) : 初始化变量,跳转到步骤3
- (L_000d -- L_0016) : 取出数组索引为局部变量索引2(即num2)的值并赋值局部变量索引为1(即num),局部变量索引为2(即num2) 加一
- (L_0017 -- L_001f) : 局部变量索引为2(即num2) 与 数组长度比较,若小于跳转步骤2,否则结束
从上面分析可知,这是一个循环结构,并在循环体取出数组值,上面IL大概是下面形式:
for(int num1 = 0; num1 < array.Length; num1++) { int num= array[num1]; }
延伸
我们知道除了上面那种方法,遍历枚举外,还有一种常用的方法遍历枚举
public IEnumerator<Student> GetEnumerator() { for (int i = 0; i < list.Count; i++) { yield return list[i]; } }
其实yield字段会自动生成一个实现IEnumerator类,所以这种方法最终的枚举器还是IEnumerator,这只是.Net的语法糖
下一篇关键字
既然这里提到yield关键字,下一篇写yield的关键字。关于yield园子里的老赵已经详细解释过人肉反编译使用yield关键字的方法。不敢班门弄斧,只是做一个完整的系列,所以按照自己理解的再整理一次。