购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

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

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


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;

}

}

///<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];}

}

}


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


IMyEnumerable list=new MyList();


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


MyEnumerator enumerator2=new MyEnumerator(list);

while(enumerator2.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代替会是什么样子?代码如下所示:


foreach(var current in list)

{

//省略了object current=enumerator.Current;

}


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


.maxstack 1

.locals init([0]class[mscorlib]System.Collections.Generic.List1<object>list,

[1]valuetype[mscorlib]System.Collections.Generic.List1/

Enumerator<object>CS$5$0000)

IL_0000:newobj instance void class

[mscorlib]System.Collections.Generic.List1<object>::.ctor()

IL_0005:stloc.0

IL_0006:ldloc.0

IL_0007:callvirt instance valuetype

[mscorlib]System.Collections.Generic.List1/Enumerator<!0>class

[mscorlib]System.Collections.Generic.List1<object>::GetEnumerator()

IL_000c:stloc.1

.try

{

IL_000d:br.s IL_0017

IL_000f:ldloca.s CS$5$0000

IL_0011:call instance!0 valuetype[mscorlib]

System.Collections.Generic.List1/Enumerator<object>::get_Current()

IL_0016:pop

IL_0017:ldloca.s CS$5$0000

IL_0019:call instance bool valuetype[mscorlib]

System.Collections.Generic.List1/Enumerator<object>::MoveNext()

IL_001e:brtrue.s IL_000f

IL_0020:leave.s IL_0030

}//end.try

finally

{

IL_0022:ldloca.s CS$5$0000

IL_0024:constrained.valuetype[mscorlib]

System.Collections.Generic.List1/Enumerator<object>

IL_002a:callvirt instance void[mscorlib]System.IDisposable::Dispose()

IL_002f:endfinally

}//end handler

IL_0030:ret


在IL代码的开始处,可以看到声明了一个Enumerator的变量:CS$5$0000。在整个代码执行过程中,虽然C#代码中看不到针对集合对象的方法调用,但是研究IL代码就可以看出,运行时还是会调用集合类型的get_Current和MoveNext方法:


IL_0019:call instance bool valuetype

[mscorlib]System.Collections.Generic.List1/Enumerator<object>::MoveNext()

IL_001e:brtrue.s IL_000f


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


while(enumerator.MoveNext())

{

object current=enumerator.Current;

}


继续分析IL代码我们还可以知道,foreach循环除了可以提供简化的语法外,还有另外两个优势:

❑自动将代码置入try-finally块。

❑若类型实现了IDispose接口,它会在循环结束后自动调用Dispose方法。

如果用Reflector进行反编译,则对应的C#代码如下所示:


List<object>list=new List<object>();

using(List<object>.Enumerator CS$5$0000=list.GetEnumerator())

{

while(CS$5$0000.MoveNext())

{

object current=CS$5$0000.Current;

}

}


注意 using是try-finally的语法糖。上面的代码等同于:


List<object>.Enumerator CS$5$0000=list.GetEnumerator();

try

{

//循环

}

finally

{

CS$5$0000.Dispose();

} aoOcaj8lEJMrOojHwyP3mPB5m5SGE6QctEIni0ldSp51xRZ7grJWtKrNgkZ7Y3a/


点击中间区域
呼出菜单
上一章
目录
下一章
×

打开