今天是我看《編寫高質量代碼:改善C#程序的157個建議》第二遍的時候了,看完這本書的確是受益匪淺,學到了很多東西,也明白了很多道理。
里面的代碼我每個都調試了一遍,有時候是有些出入的,可能是作者寫的書比較早,使用的開發環境比較舊,也許是我的學習還不到家,今天在看建議17的時候,發現了一些小問題,不是很大,是小問題,記錄下來,當別人看到的時候可以起到修正的作用。
可能很多人和我一樣,沒有太在乎for和foreach的區別,也沒有深究其底層發生的一些東西,看了文章才發現里面的東西還真是不少。
好了,我們從頭聊一下,我對foreach的認識。
Gof的23種設計模式,我相信大家都看過,我現在也正在寫有關《設計模式》的一些文章。在這些設計模式種,有一個設計模式叫“迭代器”,這種設計模式就是針對集合對象的迭代而產生的。具體的模式我就不介紹了,FCL框架中也有針對的此模式的具體實現,具體的接口分別是IEnumerable和IEnumerator接口。迭代器模式屏蔽了各種集合的內部實現,為客戶對集合的迭代生成了一個統一接口。foreach的使用語法就是針對FCL框架中實現的迭代器的統一操作實現,簡化了語法,方便了客戶的實現。
通過該文章和對IL代碼的分析,foreach除了可以提供簡化語法外,還有另外兩個有事:
1、自動將代碼置入try-finally塊。
2、若類型實現了IDisposable接口,他會在循環結束后自動調用Dispose方法。
這是書里的原話,但是這兩大優點是有出入的,並不是所有的集合類型都會將代碼自動放入try-finally塊中。
我們先來了解一下集合概況吧,集合類型主要分為兩種,一種是線性集合,另一種是非線性集合,如圖:
1、我們先針對線性集合測試,看看結果怎么樣:
1)、線性集合里面的直接存取類型的代表:數組
int[] arra = new int[] { 1, 2, 3, 4, 5, 6, 7 }; foreach (int item in arra) { Console.WriteLine(item); }
代碼運行結果是:
我們再來看看他們所生成的IL代碼:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代碼大小 56 (0x38) .maxstack 3 .locals init ([0] int32[] arra, [1] int32[] V_1, [2] int32 V_2, [3] int32 item) IL_0000: nop IL_0001: ldc.i4.7 IL_0002: newarr [mscorlib]System.Int32 IL_0007: dup IL_0008: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=28' '<PrivateImplementationDetails>'::'447E020739FB3351B9350DB35F297A3AD27669A4' IL_000d: call void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array, valuetype [mscorlib]System.RuntimeFieldHandle) IL_0012: stloc.0 IL_0013: nop IL_0014: ldloc.0 IL_0015: stloc.1 IL_0016: ldc.i4.0 IL_0017: stloc.2 IL_0018: br.s IL_002b IL_001a: ldloc.1 IL_001b: ldloc.2 IL_001c: ldelem.i4 IL_001d: stloc.3 IL_001e: nop IL_001f: ldloc.3 IL_0020: call void [mscorlib]System.Console::WriteLine(int32) IL_0025: nop IL_0026: nop IL_0027: ldloc.2 IL_0028: ldc.i4.1 IL_0029: add IL_002a: stloc.2 IL_002b: ldloc.2 IL_002c: ldloc.1 IL_002d: ldlen IL_002e: conv.i4 IL_002f: blt.s IL_001a IL_0031: call int32 [mscorlib]System.Console::Read() IL_0036: pop IL_0037: ret } // end of method Program::Main
我們針對數組執行foreach迭代,但是在所生成的IL代碼中並沒有看到try-finally中,最起碼連try和finally這個兩個字樣都沒看到,書中說foreach遍歷集合會自動生成try-finally塊,這是不准確的,最起碼數組沒有生成try-finally代碼塊。
2)、然后我們再測試一下string類型,看看他的運行結果怎么樣!
代碼如下:
string dd = "我是中國人"; foreach (var item in dd) { Console.WriteLine(item); }
執行效果如圖:
讓我們再來看看它所生成的IL代碼吧,不要驚訝:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代碼大小 51 (0x33) .maxstack 2 .locals init ([0] string dd, [1] string V_1, [2] int32 V_2, [3] char item) IL_0000: nop IL_0001: ldstr bytearray (11 62 2F 66 2D 4E FD 56 BA 4E ) // .b/f-N.V.N IL_0006: stloc.0 IL_0007: nop IL_0008: ldloc.0 IL_0009: stloc.1 IL_000a: ldc.i4.0 IL_000b: stloc.2 IL_000c: br.s IL_0023 IL_000e: ldloc.1 IL_000f: ldloc.2 IL_0010: callvirt instance char [mscorlib]System.String::get_Chars(int32) IL_0015: stloc.3 IL_0016: nop IL_0017: ldloc.3 IL_0018: call void [mscorlib]System.Console::WriteLine(char) IL_001d: nop IL_001e: nop IL_001f: ldloc.2 IL_0020: ldc.i4.1 IL_0021: add IL_0022: stloc.2 IL_0023: ldloc.2 IL_0024: ldloc.1 IL_0025: callvirt instance int32 [mscorlib]System.String::get_Length() IL_002a: blt.s IL_000e IL_002c: call int32 [mscorlib]System.Console::Read() IL_0031: pop IL_0032: ret } // end of method Program::Main
這里面也沒有生成try-finally代碼塊,說明並不是所有的集合都會自動生成try-finally代碼塊的。
3)、我們繼續測試一下Dictionary<TKey,TValue>,List<T>,Queue<T>,Stack<T>,ArrayList類型:
(1)、Dictionary<TKey,TValue>測試代碼:
Dictionary<string, string> dictionary = new Dictionary<string, string>(); dictionary.Add("1", "11"); dictionary.Add("2", "22"); dictionary.Add("3", "33"); dictionary.Add("4", "44"); dictionary.Add("5", "55"); dictionary.Add("6", "66"); dictionary.Add("7", "77"); foreach (var item in dictionary) { Console.WriteLine(item.Key + "----" + item.Value); }
字典類型所生成IL代碼如下:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代碼大小 209 (0xd1) .maxstack 3 .locals init ([0] class [mscorlib]System.Collections.Generic.Dictionary`2<string,string> dictionary, [1] valuetype [mscorlib]System.Collections.Generic.Dictionary`2/Enumerator<string,string> V_1, [2] valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string> item) IL_0000: nop IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldstr "1" IL_000d: ldstr "11" IL_0012: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0, !1) IL_0017: nop IL_0018: ldloc.0 IL_0019: ldstr "2" IL_001e: ldstr "22" IL_0023: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0, !1) IL_0028: nop IL_0029: ldloc.0 IL_002a: ldstr "3" IL_002f: ldstr "33" IL_0034: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0, !1) IL_0039: nop IL_003a: ldloc.0 IL_003b: ldstr "4" IL_0040: ldstr "44" IL_0045: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0, !1) IL_004a: nop IL_004b: ldloc.0 IL_004c: ldstr "5" IL_0051: ldstr "55" IL_0056: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0, !1) IL_005b: nop IL_005c: ldloc.0 IL_005d: ldstr "6" IL_0062: ldstr "66" IL_0067: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0, !1) IL_006c: nop IL_006d: ldloc.0 IL_006e: ldstr "7" IL_0073: ldstr "77" IL_0078: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::Add(!0, !1) IL_007d: nop IL_007e: nop IL_007f: ldloc.0 IL_0080: callvirt instance valuetype [mscorlib]System.Collections.Generic.Dictionary`2/Enumerator<!0,!1> class [mscorlib]System.Collections.Generic.Dictionary`2<string,string>::GetEnumerator() IL_0085: stloc.1 .try { IL_0086: br.s IL_00b0 IL_0088: ldloca.s V_1 IL_008a: call instance valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<!0,!1> valuetype [mscorlib]System.Collections.Generic.Dictionary`2/Enumerator<string,string>::get_Current() IL_008f: stloc.2 IL_0090: nop IL_0091: ldloca.s item IL_0093: call instance !0 valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string>::get_Key() IL_0098: ldstr "----" IL_009d: ldloca.s item IL_009f: call instance !1 valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string,string>::get_Value() IL_00a4: call string [mscorlib]System.String::Concat(string, string, string) IL_00a9: call void [mscorlib]System.Console::WriteLine(string) IL_00ae: nop IL_00af: nop IL_00b0: ldloca.s V_1 IL_00b2: call instance bool valuetype [mscorlib]System.Collections.Generic.Dictionary`2/Enumerator<string,string>::MoveNext() IL_00b7: brtrue.s IL_0088 IL_00b9: leave.s IL_00ca } // end .try finally { IL_00bb: ldloca.s V_1 IL_00bd: constrained. valuetype [mscorlib]System.Collections.Generic.Dictionary`2/Enumerator<string,string> IL_00c3: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_00c8: nop IL_00c9: endfinally } // end handler IL_00ca: call int32 [mscorlib]System.Console::Read() IL_00cf: pop IL_00d0: ret } // end of method Program::Main
哈哈哈,終於出現了,自動生成try-finally代碼塊,並且調用了Dispose方法。
(2)List<T>,Queue<T>,Stack<T>,ArrayList測試代碼如下:
List<int> list = new List<int>() { 1, 2 }; foreach (var item in list) { Console.WriteLine(item); }
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代碼大小 83 (0x53) .maxstack 3 .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> list, [1] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> V_1, [2] int32 item) IL_0000: nop IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor() IL_0006: dup IL_0007: ldc.i4.1 IL_0008: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0) IL_000d: nop IL_000e: dup IL_000f: ldc.i4.2 IL_0010: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0) IL_0015: nop IL_0016: stloc.0 IL_0017: nop IL_0018: ldloc.0 IL_0019: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator() IL_001e: stloc.1 .try { IL_001f: br.s IL_0032 IL_0021: ldloca.s V_1 IL_0023: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current() IL_0028: stloc.2 IL_0029: nop IL_002a: ldloc.2 IL_002b: call void [mscorlib]System.Console::WriteLine(int32) IL_0030: nop IL_0031: nop IL_0032: ldloca.s V_1 IL_0034: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext() IL_0039: brtrue.s IL_0021 IL_003b: leave.s IL_004c } // end .try finally { IL_003d: ldloca.s V_1 IL_003f: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> IL_0045: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_004a: nop IL_004b: endfinally } // end handler IL_004c: call int32 [mscorlib]System.Console::Read() IL_0051: pop IL_0052: ret } // end of method Program::Main
其他的代碼我就不貼了,都生成了try-finally代碼塊,如果類型實現了IDisposable接口,也會調用Dispose方法。
2、我們再測試一下非線性集合是否會自動產生相關代碼
我們測試一下HashSet<T>代碼,看看效果怎么樣。
HashSet<string> hash = new HashSet<string>(); hash.Add("1"); hash.Add("2"); hash.Add("2"); hash.Add("3"); hash.Add("4"); hash.Add("5"); foreach (var item in hash) { Console.WriteLine(item); }
運行效果圖:
最后我們看看IL代碼:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代碼大小 139 (0x8b) .maxstack 2 .locals init ([0] class [System.Core]System.Collections.Generic.HashSet`1<string> hash, [1] valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<string> V_1, [2] string item) IL_0000: nop IL_0001: newobj instance void class [System.Core]System.Collections.Generic.HashSet`1<string>::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldstr "1" IL_000d: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0) IL_0012: pop IL_0013: ldloc.0 IL_0014: ldstr "2" IL_0019: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0) IL_001e: pop IL_001f: ldloc.0 IL_0020: ldstr "2" IL_0025: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0) IL_002a: pop IL_002b: ldloc.0 IL_002c: ldstr "3" IL_0031: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0) IL_0036: pop IL_0037: ldloc.0 IL_0038: ldstr "4" IL_003d: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0) IL_0042: pop IL_0043: ldloc.0 IL_0044: ldstr "5" IL_0049: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`1<string>::Add(!0) IL_004e: pop IL_004f: nop IL_0050: ldloc.0 IL_0051: callvirt instance valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<!0> class [System.Core]System.Collections.Generic.HashSet`1<string>::GetEnumerator() IL_0056: stloc.1 .try { IL_0057: br.s IL_006a IL_0059: ldloca.s V_1 IL_005b: call instance !0 valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<string>::get_Current() IL_0060: stloc.2 IL_0061: nop IL_0062: ldloc.2 IL_0063: call void [mscorlib]System.Console::WriteLine(string) IL_0068: nop IL_0069: nop IL_006a: ldloca.s V_1 IL_006c: call instance bool valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<string>::MoveNext() IL_0071: brtrue.s IL_0059 IL_0073: leave.s IL_0084 } // end .try finally { IL_0075: ldloca.s V_1 IL_0077: constrained. valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<string> IL_007d: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0082: nop IL_0083: endfinally } // end handler IL_0084: call int32 [mscorlib]System.Console::Read() IL_0089: pop IL_008a: ret } // end of method Program::Main
好了,總結一下吧,如果不涉及到修改集合元素,一般情況下使用Foreach比較好,在我測試的代碼中,數組和String數據類型不會生成try-finally代碼塊,里面有一些出入而已,但是這並不影響Foreach的使用。所以我們在具體的編碼過程中,盡量多的使用Foreach吧,沒關系。