• 编写高质量代码改善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吧,没关系。

  • 相关阅读:
    DG查看恢复进度
    dataguard主备延迟多长时间的查询方法
    DG动态性能视图详解
    Django之ORM的增删改查操作流程
    IPython
    render函数和redirect函数的区别+反向解析
    http状态码
    图的基本概念
    图的遍历
    vue之webpack打包工具的使用
  • 原文地址:https://www.cnblogs.com/PatrickLiu/p/7204985.html
Copyright © 2020-2023  润新知