背景
網上流傳“沒有用.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關鍵字的方法。不敢班門弄斧,只是做一個完整的系列,所以按照自己理解的再整理一次。