编写高质量代码改善C#程序的157个建议——建议17:多数情况下使用foreach进行循环遍历
建议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[];
- 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 = ;
- MyList myList;
- public MyEnumerator(MyList myList)
- {
- this.myList = myList;
- }
- public bool MoveNext()
- {
- if (index + > myList.Count)
- {
- index = ;
- return false;
- }
- else
- {
- index++;
- return true;
- }
- }
- public object Current
- {
- get { return myList[index - ]; }
- }
- }
- static void Main(string[] args)
- {
- //使用接口IMyEnumerable代替MyList
- IMyEnumerable list = new MyList();
- //得到迭代器,在循环中针对迭代器编码,而不是集合MyList
- IMyEnumerator enumerator = list.GetEnumerator();
- for (int i = ; 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 = ; 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 = ; 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
- .locals init ([] class [mscorlib]System.Collections.Generic.ICollection`<object> list,
- [] object current,
- [] class [mscorlib]System.Collections.Generic.IEnumerator`<object> CS$$,
- [] bool CS$$)
- IL_0000: nop
- IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`<object>::.ctor()
- IL_0006: stloc.0
- IL_0007: nop
- IL_0008: ldloc.0
- IL_0009: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`<!> class [mscorlib]System.Collections.Generic.IEnumerable`<object>::GetEnumerator()
- IL_000e: stloc.2
- .try
- {
- IL_000f: br.s IL_001a
- IL_0011: ldloc.2
- IL_0012: callvirt instance ! class [mscorlib]System.Collections.Generic.IEnumerator`<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个建议》陆敏技
编写高质量代码改善C#程序的157个建议——建议17:多数情况下使用foreach进行循环遍历的更多相关文章
- 编写高质量代码改善C#程序的157个建议[1-3]
原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...
- 读书--编写高质量代码 改善C#程序的157个建议
最近读了陆敏技写的一本书<<编写高质量代码 改善C#程序的157个建议>>书写的很好.我还看了他的博客http://www.cnblogs.com/luminji . 前面部 ...
- 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试
建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...
- 编写高质量代码改善C#程序的157个建议——建议156:利用特性为应用程序提供多个版本
建议156:利用特性为应用程序提供多个版本 基于如下理由,需要为应用程序提供多个版本: 应用程序有体验版和完整功能版. 应用程序在迭代过程中需要屏蔽一些不成熟的功能. 假设我们的应用程序共有两类功能: ...
- 编写高质量代码改善C#程序的157个建议——建议155:随生产代码一起提交单元测试代码
建议155:随生产代码一起提交单元测试代码 首先提出一个问题:我们害怕修改代码吗?是否曾经无数次面对乱糟糟的代码,下决心进行重构,然后在一个月后的某个周一,却收到来自测试版的报告:新的版本,没有之前的 ...
- 编写高质量代码改善C#程序的157个建议——建议154:不要过度设计,在敏捷中体会重构的乐趣
建议154:不要过度设计,在敏捷中体会重构的乐趣 有时候,我们不得不随时更改软件的设计: 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个 ...
- 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释
建议153:若抛出异常,则必须要注释 有一种必须加注释的场景,即使异常.如果API抛出异常,则必须给出注释.调用者必须通过注释才能知道如何处理那些专有的异常.通常,即便良好的命名也不可能告诉我们方法会 ...
- 编写高质量代码改善C#程序的157个建议——建议152:最少,甚至是不要注释
建议152:最少,甚至是不要注释 以往,我们在代码中不写上几行注释,就会被认为是钟不负责任的态度.现在,这种观点正在改变.试想,如果我们所有的命名全部采用有意义的单词或词组,注释还有多少存在的价值. ...
- 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量
建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...
- 编写高质量代码改善C#程序的157个建议——建议150:使用匿名方法、Lambda表达式代替方法
建议150:使用匿名方法.Lambda表达式代替方法 方法体如果过小(如小于3行),专门为此定义一个方法就会显得过于繁琐.比如: static void SampeMethod() { List< ...
随机推荐
- Tensorflow的采样方法:candidate sampling(zhuan)
zhuanzi:https://blog.csdn.net/u010223750/article/details/69948463 采样介绍 假如我们有一个多分类任务或者多标签分类任务,给定训练集(x ...
- Android开发SQLite数据库的创建
package com.example.db; import android.content.Context; import android.database.sqlite.SQLiteDatabas ...
- 如何设计并使用FireMonkeyStyle
如何设计并使用FireMonkeyStyle FireMonkey使用Style来控制控件的显示方式. 每个控件都有一个StyleLookup属性,FireMonkey就是通过控件的这个属性来在当前窗 ...
- cowboy的例子
大体参考的这里,非常感谢他的例子 开发的时候先下载好cowboy的库,放到~/.erlang里面 code:add_pathz("/Users/mmc/erlang/3rd_libs/cow ...
- unittest框架+ HTMLTestRunner 出报告时,展示控制台信息 不同展示的参数写法 加verbosity
加verbosity参数 没有加的时候展示: 参考: 来源: https://www.cnblogs.com/tomweng/p/6609937.html 介绍: HTMLTestRunner 是 ...
- Java-Runoob:Java switch case
ylbtech-Java-Runoob:Java switch case 1.返回顶部 1. Java switch case 语句 switch case 语句判断一个变量与一系列值中某个值是否相等 ...
- 微信小程序之目录结构
小程序,功能不会太多,页面不会太多. 正常情况下,会包含首页,分类页面,个人中心页面,导航页面,其他页面等等. 我们首先要把页面结构布置好,把架子搭建好. 剩下的就是配置一些内容,小程序的基本信息,接 ...
- Docker构建ssh镜像
FROM ubuntu MAINTAINER ggzone xxx@live.com ENV REFRESHED_AT 2015-10-21 RUN apt-get -qqy update & ...
- python's sixteenth day for me 员工信息表
import os user_dic = { 'username':None, 'password':None, 'login':True } flag = False name_list = ['i ...
- [原创]Spring JdbcTemplate 使用总结与经验分享
引言 近期开发的几个项目,均是基于Spring boot框架的web后端项目,使用JdbcTemplate执行数据库操作,实际开发过程中,掌握了一些有效的开发经验,踩过一些坑,在此做个记录及总结,与各 ...