建议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进行循环遍历的更多相关文章

  1. 编写高质量代码改善C#程序的157个建议[1-3]

    原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...

  2. 读书--编写高质量代码 改善C#程序的157个建议

    最近读了陆敏技写的一本书<<编写高质量代码  改善C#程序的157个建议>>书写的很好.我还看了他的博客http://www.cnblogs.com/luminji . 前面部 ...

  3. 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试

    建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...

  4. 编写高质量代码改善C#程序的157个建议——建议156:利用特性为应用程序提供多个版本

    建议156:利用特性为应用程序提供多个版本 基于如下理由,需要为应用程序提供多个版本: 应用程序有体验版和完整功能版. 应用程序在迭代过程中需要屏蔽一些不成熟的功能. 假设我们的应用程序共有两类功能: ...

  5. 编写高质量代码改善C#程序的157个建议——建议155:随生产代码一起提交单元测试代码

    建议155:随生产代码一起提交单元测试代码 首先提出一个问题:我们害怕修改代码吗?是否曾经无数次面对乱糟糟的代码,下决心进行重构,然后在一个月后的某个周一,却收到来自测试版的报告:新的版本,没有之前的 ...

  6. 编写高质量代码改善C#程序的157个建议——建议154:不要过度设计,在敏捷中体会重构的乐趣

    建议154:不要过度设计,在敏捷中体会重构的乐趣 有时候,我们不得不随时更改软件的设计: 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个 ...

  7. 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释

    建议153:若抛出异常,则必须要注释 有一种必须加注释的场景,即使异常.如果API抛出异常,则必须给出注释.调用者必须通过注释才能知道如何处理那些专有的异常.通常,即便良好的命名也不可能告诉我们方法会 ...

  8. 编写高质量代码改善C#程序的157个建议——建议152:最少,甚至是不要注释

    建议152:最少,甚至是不要注释 以往,我们在代码中不写上几行注释,就会被认为是钟不负责任的态度.现在,这种观点正在改变.试想,如果我们所有的命名全部采用有意义的单词或词组,注释还有多少存在的价值. ...

  9. 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量

    建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...

  10. 编写高质量代码改善C#程序的157个建议——建议150:使用匿名方法、Lambda表达式代替方法

    建议150:使用匿名方法.Lambda表达式代替方法 方法体如果过小(如小于3行),专门为此定义一个方法就会显得过于繁琐.比如: static void SampeMethod() { List< ...

随机推荐

  1. VC++ MFC SQL ADO数据库访问技术使用的基本步骤及方法

    1.首先,要用#import语句来引用支持ADO的组件类型库(*.tlb),其中类型库可以作为可执行程序 (DLL.EXE等)的一部分被定位在其自身程序中的附属资源里,如:被定位在msado15.dl ...

  2. 杂项-公司-百科:华特·迪士尼-un

    ylbtech-杂项-公司-百科:华特·迪士尼 华特·迪士尼(Walt Disney,全名Walter Elias Disney,又译沃尔特·迪士尼,1901年12月5日—1966年12月15日),出 ...

  3. C#之Application.DoEvents()

    Application.DoEvents()的最大作用就是时时响应, 可以看做是个线程的一个封装 private void button1_Click(object sender, EventArgs ...

  4. maven学习4 使用Maven构建Spring项目

    1. 新建一个Web项目 参考之前的博客 2.修改 pom.xml,添加Spring依赖 <project xmlns="http://maven.apache.org/POM/4.0 ...

  5. Deep Learning 学习笔记(4):Logistic Regression 逻辑回归

    逻辑回归主要用于解决分类问题,在现实中有更多的运用, 正常邮件or垃圾邮件 车or行人 涨价or不涨价 用我们EE的例子就是: 高电平or低电平 同时逻辑回归也是后面神经网络到深度学习的基础. (原来 ...

  6. linux 下安装mysql-5.7.16

    1.解压tar -xvf mysql的包 tar -xvf mysql-5.7.16-1.el6.x86_64.rpm-bundle.tar(mysql 官网中即可找到) 2.查看是否需要卸载安装时候 ...

  7. classmethod VS staticmethod

  8. HDFS的介绍

    设计思想 分而治之:将大文件.大批量文件,分布式存放在大量服务器上,以便于采取分而治之的方式对海量数据进行运算分析: 在大数据系统中作用:为各类分布式运算框架(如:mapreduce,spark,te ...

  9. [原创]Java使用反射及自定义注解实现对象差异性比较

    Java项目C中 有一处逻辑,对于资源数据(类型为ResourceItem,拥有int/double/boolean/String类型数十个字段),需要比对资源数据每次变更的差异,并描述出变更情况.并 ...

  10. Python基础学习三 字典、元组

    一.元组 元组,提示别人,这个值是不能被改变的,元组的定义方式是用(),小括号: 元组只有两个方法,那就是count和index mysql1 = ('127.0.0.1',3306,'my','ro ...