編寫高質量代碼改善C#程序的157個建議:第17個建議之多數情況下使用foreach進行循環遍歷


      今天是我看《編寫高質量代碼:改善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吧,沒關系。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM