建议17:多数情况下使用foreach进行循环遍历

转载 2016年08月31日 13:22:52

建议17:多数情况下使用foreach进行循环遍历

由于本建议涉及集合的遍历,所以在开始讲解本建议之前,我们不妨来设想一下如何对结合进行遍历。假设存在一个数组,其遍历模式可以采用依据索引来进行遍历的方法;又假设存在一个HashTable,其遍历模式可能是按照键值来进行遍历。无论是哪个集合,如果他们的遍历没有一个公共的接口,那么客户端在进行遍历时,相当于是对具体类型进行了编码。这样一来,当需求发生变化时,必须修改我们的代码。而且,由于客户端代码过多地关注了集合内部的实现,代码的可移植性就会变得很差,这直接违反了面向对象的开闭原则。于是,迭代器模式就诞生了。现在,不要管FCL中如何实现该模式的,我们先来实现一个自己的迭代器模式。

复制代码
     /// <summary>
        /// 要求所有的迭代器全部实现该接口
        /// </summary>
        interface IMyEnumerator
        {
            bool MoveNext();
            object Current { get; }
        }

        /// <summary>
        /// 要求所有的集合实现该接口
        /// 这样一来,客户端就可以针对该接口编码,
        /// 而无须关注具体的实现
        /// </summary>
        interface IMyEnumerable
        {
            IMyEnumerator GetEnumerator();
            int Count { get; }
        }

        class MyList : IMyEnumerable
        {
            object[] items = new object[10];
            IMyEnumerator myEnumerator;

            public object this[int i]
            {
                get { return items[i]; }
                set { this.items[i] = value; }
            }

            public int Count
            {
                get { return items.Length; }
            }

            public IMyEnumerator GetEnumerator()
            {
                if (myEnumerator == null)
                {
                    myEnumerator = new MyEnumerator(this);
                }
                return myEnumerator;
            }
        }

        class MyEnumerator : IMyEnumerator
        {
            int index = 0;
            MyList myList;
            public MyEnumerator(MyList myList)
            {
                this.myList = myList;
            }

            public bool MoveNext()
            {
                if (index + 1 > myList.Count)
                {
                    index = 1;
                    return false;
                }
                else
                {
                    index++;
                    return true;
                }
            }

            public object Current
            {
                get { return myList[index - 1]; }
            }
        }
复制代码
复制代码
        static void Main(string[] args)
        {
            //使用接口IMyEnumerable代替MyList
            IMyEnumerable list = new MyList();
            //得到迭代器,在循环中针对迭代器编码,而不是集合MyList
            IMyEnumerator enumerator = list.GetEnumerator();
            for (int i = 0; i < list.Count; i++)
            {
                object current = enumerator.Current;
                enumerator.MoveNext();
            }
            while (enumerator.MoveNext())
            {
                object current = enumerator.Current;
            }
        }
复制代码

 

MyList模拟了一个集合类,它继承了接口IMyEnumerable,这样,在客户端调用的时候,我们就可以直接调用IMyEnumerable来声明变量,如代码中的一下语句:

IMyEnumerable list=new MyList();

如果未来我们新增了其他的集合类,那么针对list的编码即使不做修改也能运行良好。在IMyEnumerable中声明了GetEnumerator方法返回一个继承了IMyEnumerator的对象。在MyList的内部,默认返回MyEnumerator,MyEnumerator就是迭代器的一个实现,如果对于迭代的需求有变化,可以重新开发一个迭代器(如下所示),然后在客户端迭代的时候使用该迭代器。

复制代码
            //使用接口IMyEnumerable代替MyList
            IMyEnumerable list = new MyList();
            //得到迭代器,在循环中针对迭代器编码,而不是集合MyList
            IMyEnumerator enumerator2 = new MyEnumerator(list);
       //for调用
for (int i = 0; i < list.Count; i++) { object current = enumerator2.Current; enumerator.MoveNext(); }
       //while调用
while (enumerator.MoveNext()) { object current = enumerator2.Current; }
复制代码

 

在客户端的代码中,我们在迭代的过程中分别演示了for循环和while循环,到那时因为使用了迭代器的缘故,两个循环都没有针对MyList编码,而是实现了对迭代器的编码。

 

理解了自己实现的迭代器模式,相当于理解了FCL中提供的对应模式。以上代码中,在接口和类型中都加入了“My”字样,其实,FCL中有与之相对应的接口和类型,只不过为了演示需要,增加了其中部分内容,但是大致思路是一样的。使用FCL中相应类型进行客户端代码编写,大致应该下面这样:

复制代码
            ICollection<object> list = new List<object>();
            IEnumerator enumerator = list.GetEnumerator();

for (int i = 0; i < list.Count; i++) { object current = enumerator.Current; enumerator.MoveNext(); }
while (enumerator.MoveNext()) { object current = enumerator.Current; }
复制代码

 

 但是,无论是for循环还是while循环,都有些啰嗦,于是,foreach就出现了。

            foreach (var current in list)
            {
                //省略了 object current = enumerator.Current;
            }

 

 可以看到,采用foreach最大限度地简化了代码。它用于遍历一个继承了IEnumerable或IEnumerable<T>接口的集合元素。借助IL代码,我们查看使用foreach到底发生了什么事情:

复制代码
.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       62 (0x3e)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Collections.Generic.ICollection`1<object> list,
           [1] object current,
           [2] class [mscorlib]System.Collections.Generic.IEnumerator`1<object> CS$5$0000,
           [3] bool CS$4$0001)
  IL_0000:  nop
  IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<object>::.ctor()
  IL_0006:  stloc.0
  IL_0007:  nop
  IL_0008:  ldloc.0
  IL_0009:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<object>::GetEnumerator()
  IL_000e:  stloc.2
  .try
  {
    IL_000f:  br.s       IL_001a
    IL_0011:  ldloc.2
    IL_0012:  callvirt   instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<object>::get_Current()
    IL_0017:  stloc.1
    IL_0018:  nop
    IL_0019:  nop
    IL_001a:  ldloc.2
    IL_001b:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    IL_0020:  stloc.3
    IL_0021:  ldloc.3
    IL_0022:  brtrue.s   IL_0011
    IL_0024:  leave.s    IL_0036
  }  // end .try
  finally
  {
    IL_0026:  ldloc.2
    IL_0027:  ldnull
    IL_0028:  ceq
    IL_002a:  stloc.3
    IL_002b:  ldloc.3
    IL_002c:  brtrue.s   IL_0035
    IL_002e:  ldloc.2
    IL_002f:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0034:  nop
    IL_0035:  endfinally
  }  // end handler
  IL_0036:  nop
  IL_0037:  call       int32 [mscorlib]System.Console::Read()
  IL_003c:  pop
  IL_003d:  ret
} // end of method Program::Main
复制代码

 

 查看IL代码就可以看出,运行时还是会调用get_Current()MoveNext()方法。

在调用完MoveNext()方法后,如果结果是true,跳转到循环开始处。实际上foreach循环和while循环是一样的:

            while (enumerator.MoveNext())
            {
                object current = enumerator.Current;
            }

 

foreach循环除了可以提供简化的语法外,还有另外两个优势:

  • 自动将代码置入try finally块
  • 若类型实现了IDisposable接口,它会在循环结束后自动调用Dispose方法。

 

 

 

转自:《编写高质量代码改善C#程序的157个建议》陆敏技

相关文章推荐

js中的循环遍历数组中的元素,ES6(for-of)、ES5(forEach、for-in)、通用(for(i=0;i<length;i++))

我们如何遍历数组中的元素?for (var index = 0; index < myArray.length; index++) { console.log(myArray[index]); }...

javascript通用循环遍历方法forEach

上一次的错误太多,排版也出现了问题,重写了一遍,希望大家支持.   循环遍历一个元素是开发中最常见的需求之一,那么让我们来看一个由框架BASE2和Jquery的结合版本吧.   ...

Velocity学习笔记----foreach双重循环遍历list

#set($list1=[{"asd": "appColumnManage","dbcol_ext_template_param": [{"param_key":"location_2","...

Javascript 数组循环遍历之forEach

目录(?)[-] js 数组循环遍历forEach 函数让IE兼容forEach方法如何跳出循环 1.  js 数组循环遍历。 数组循环变量,最先想到的就是 for(va...
  • kdsde
  • kdsde
  • 2013-03-14 13:23
  • 733

PHP学习笔记——使用list(),each(),while()循环遍历数组

]
  • iheyu
  • iheyu
  • 2017-05-29 19:07
  • 109

循环遍历密码

Json 循环遍历解析

java 枚举 循环遍历以及一些简单常见的使用

什么时候想用枚举类型: 有时候,在设计一个java model对象的时候,你需要一些具体的常量字符串之类的东西,这个东西又没必要跟整个项目的全局常量放在一起,就放在model的java文件里面是最合适...

PHP学习笔记——使用for循环遍历数组

/* 使用for遍历数组 * 保证数组,一定是下标连续的索引数组 * 优点 : * 效率很高,就是数组访问方式,只不过通过循...
  • iheyu
  • iheyu
  • 2017-05-26 20:43
  • 198
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)