对于.NET Framework中的foreach使用“算得上”是很熟练了,但今天看了书才发现自己对其一点也不了解……
对于.NET Framework提供的“标准”的集合类型可以直接使用foreach,如(Array、ArrayList、HashTable),除此之外,对于自定义的集合对象,也可以使其支持foreach的使用,只要实现IEnumerable接口即可(刚提到的几种集合类型都实现了这个接口)。先看个例子(代码参考http://jangmon.blogbus.com/logs/36380490.html)
{
public IEnumerator GetEnumerator ()
{
yield return " Hello " ;
yield return " World " ;
}
}
class CustomerForeach
{
static void Main()
{
HelloCollection helloCollection = new HelloCollection();
foreach ( string s in helloCollection)
{
Console.WriteLine(s + " …… " );
}
Console.ReadKey();
}
}
运行可以看到正确的输出了“Hello…… World……”,问题的关键就在 “IEnumerator”的 “IEnumerable.GetEnumerator()”这个方法,以及yield关键字,单步运行程序会发现程序运行至foreach语句之后跳转至HelloCollection中的GetEnumerator方法,然后遇到“yield return”就返回,接着执行下一个,直到返回所有的yield……
修改上述代码,比方说改为如下代码,先猜测下结果,然后运行看输出什么。
{
public IEnumerator GetEnumerator()
{
Console.WriteLine( " 跳转至GetEnumerator方法 " );
Console.WriteLine( " 即将输出 " );
yield return " Hello " ;
Console.WriteLine( " 下一个输出 " );
yield return " World " ;
}
}
class CustomerForeach
{
static void Main()
{
HelloCollection helloCollection = new HelloCollection();
foreach ( string s in helloCollection)
{
Console.WriteLine( " foreach方法 " );
Console.WriteLine(s );
}
Console.ReadKey();
}
}
结果输出的是
即将输出:
foreach方法
Hello
下一个输出:
foreach方法
看明白了?遇到一个yield return 就返回到foreach,而且在foreach内部使用的s就是返回的值……
再看个代码
{
static void Main()
{
Peoples ps = new Peoples();
foreach (People p in ps)
{
Console.WriteLine(p);
}
Console.ReadKey();
}
}
class People
{
public string Name;
public int Age;
// 重写基类object的Tostring方法()以按一下格式提供有意义的信息
// 姓名:年龄
public override string ToString()
{
// return base.ToString();
return Name + " : " + Age.ToString();
}
}
class Peoples:IEnumerable
{
private People[] objects;
private Random ran = new Random();
private void FillElements()
{
objects = new People[ 10 ];
for ( int i = 0 ; i < 10 ; i ++ )
{
objects[i] = new People();
objects[i].Name = " People " + (i + 1 ).ToString();
objects[i].Age = ran.Next( 1 , 100 );
}
}
public Peoples()
{
FillElements();
}
#region IEnumerable 成员
IEnumerator IEnumerable.GetEnumerator()
{
// throw new NotImplementedException();
foreach (People people in objects)
{
yield return people;
}
}
#endregion
}
这个自己测试吧,通过查找可以知道IEnumerable里就一个方法是IEnumerator GetEnumerator();而IEnumerator接口原型如下(查询VS2008文档得知)
{
// 获取集合中的当前元素。
object Current { get ; }
// 将枚举数推进到集合的下一个元素。
bool MoveNext();
// 将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。
void Reset();
}
以下是自带的解释
枚举数可用于读取集合中的数据,但不能用于修改基础集合。
最初,枚举数定位在集合中第一个元素前。Reset 方法还会将枚举数返回到此位置。在此位置上,Current 属性未定义。因此,在读取 Current 的值之前,必须调用 MoveNext 方法将枚举数提前到集合的第一个元素。
在调用 MoveNext 或 Reset 之前,Current 返回同一对象。MoveNext 将 Current 设置为下一个元素。
如果 MoveNext 越过集合的末尾,则枚举数将被放置在此集合中最后一个元素的后面,而且 MoveNext 返回 false 。当枚举数位于此位置时,对 MoveNext 的后续调用也返回 false 。如果上一个 MoveNext 调用返回 false ,则 Current 未定义。若要再次将 Current 设置为集合的第一个元素,可以调用 Reset,然后再调用 MoveNext。
只要集合保持不变,枚举数就保持有效。如果对集合进行更改(如添加、修改或删除元素),则枚举数将失效且不可恢复,而且其行为是不确定的。
枚举数没有对集合的独占访问权;因此,枚举通过集合在本质上不是一个线程安全的过程。若要确保枚举过程中的线程安全,可以在整个枚举过程中锁定集合。若要允许多个线程访问集合以进行读写操作,则必须实现自己的同步。
最后在加个例子,跟上述相关的
{
static void Main()
{
int [] values = new int [ 10 ]{ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };
ArrayList arrl = new ArrayList();
arrl.Add( " Arr1 " );
arrl.Add( " Arr3 " );
arrl.Add( " Arr5 " );
Hashtable hst = new Hashtable();
hst.Add( 2 , " hst2 " );
hst.Add( 4 , " hst4 " );
hst.Add( 6 , " hst6 " );
PrintCollection(values);
PrintCollection(arrl);
PrintCollection(hst);
Console.ReadKey();
}
static void PrintCollection(IEnumerable obj)
{
// 获取迭代访问器
IEnumerator itor = obj.GetEnumerator();
// 只要集合中还有为访问的元素。movenext方法返回True
while (itor.MoveNext())
{
// itor.Current返回当前的对象
// 本例仅简单的输出其Tostring()方法的结果
Console.WriteLine(itor.Current.ToString());
}
}
}
看明白了吗?
参考书籍:金旭亮的《.NET 2.0 面向对象编程揭秘》