建议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. 初上dubbo

    编译异常 no groovy library is defined for module http://blog.csdn.net/lu_wei_wei/article/details/7601968 ...

  2. android多渠道打包牛B工具

    http://www.orchidshell.com/ 兰贝壳儿:一个Eclipse插件,为Android开发提供了多渠道打包功能和一些工具类.

  3. 新机器连接老机器遇到的ERROR

    Ansible无法连接老旧机器 报错内容: [root@BI ansible]# ansible -i /etc/ansible/hosts GameServer -m ping 10.10.113. ...

  4. java中求利息的代码

    总结:函数的重要性,懂得用哪一种函数 package com.badu; import java.util.Scanner; //输入存款金额 money.存期 year 和年利率 rate, //根 ...

  5. java写基础的九九乘法表

    package com.aaa; public class Xox { public static void main(String[] args) { for (int i = 1; i <= ...

  6. 微信JS-SDK实现自定义分享功能,分享给朋友,分享到朋友圈

    分享出去的内容,可以通过jssdk进行修改. 1.配置jssdk Wx_config.html <?php import("@.ORG.jssdk"); $jssdk = n ...

  7. Web验证码图片的生成-基于Java的实现

    验证码图片是由程序动态产生的,每次访问的内容都是随机的.那么如何采用程序动态产生图片,并能够显示在客户端页面中呢?原理很简单,对于java而言,我们首先开发一个Servlet,这个Servlet的任务 ...

  8. nodejs中的util.inspect.js

    util.inspect(object,[showHidden],[depth],[colors])是一个将任意对象转换 为字符串的方法,通常用于调试和错误输出.它至少接受一个参数 object,即要 ...

  9. 讲解一下this (作用域)

    this的指向:普通函数内的this指向全局变量 构造函数内部this指向新创建出来的对象 对象方法内的this指向的是调用该方法的对象 call,apply,bind可以改变this的指向

  10. WARNING: cell0 mapping not found - not syncing cell0

    WARNING: cell0 mapping not found - not syncing cell0