zoukankan      html  css  js  c++  java
  • 编写高质量代码改善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吧,没关系。

  • 相关阅读:
    ajax专题
    luogu P1346 电车 最短路
    luogu P1462 通往奥格瑞玛的道路 最短路
    luogu P1328 生活大爆炸版石头剪刀布
    luogu P1315 联合权值 枚举
    luogu P1156 垃圾陷阱 背包问题
    luogu P1217 回文质数 枚举
    luogu P3650 滑雪课程设计 枚举
    luogu1209 修理牛棚 贪心
    luogu P1223 排队接水 贪心
  • 原文地址:https://www.cnblogs.com/PatrickLiu/p/7204985.html
Copyright © 2011-2022 走看看