王朝百科
分享
 
 
 

C#枚举器

王朝百科·作者佚名  2012-04-17  
宽屏版  字体: |||超大  

术语表Iterator:枚举器

如果你正在创建一个表现和行为都类似于集合的类,允许类的用户使用foreach语句对集合中的成员进行枚举将会是很方便的。这在C# 2.0中比 C# 1.1更容易实现一些。作为演示,我们先在 C# 1.1中为一个简单的集合添加枚举,然后我们修改这个范例,使用新的C#2.0 枚举构建方法。

我们将以创建一个简单化的List Box作为开始,它将包含一个8字符串的数组和一个整型,这个整型用于记录数组中已经添加了多少字符串。构造函数将对数组进行初始化并使用传递进来的参数填充它。

public ListBox(params string[] initialStrings)

{

strings = new String[8];

foreach (string s in initialStrings)

{

strings[ctr++] = s;

}

}

除此以外,ListBox类还需要一个Add方法(进行添加 string 的操作) 和 一个返回数组中字符串个数的方法。

public void Add(string theString)

{

strings[ctr] = theString;

ctr++;

}

public int GetNumEntries()

{

return ctr;

}

NOTE:实际开发中,通常使用ArrayList,而不是固定大小的数组。在这里为了程序简单就没有做数组下标越界的检测。

从感觉上看,ListBox像是一个集合,如果可以使用集合中通常使用的 foreach 循环来获取listBox中的所有字符串将会是非常便利的。如此的话,可以这样书写代码:

ListBox lb = new ListBox("a", "b", "c", "d", "e", "f", "g", "h");

foreach (string s in lb) {

Console.WriteLine(s);

}

但是,会得到这样一个错误:

“Iterator.ListBox”不包含“GetEnumerator”的公共定义,因此 foreach 语句不能作用于“Iterator.ListBox”类型的变量

想要使用foreach语句,还必须实现IEnumerable 接口。

这个接口只要求实现一个方法: GetEnumerator。这个方法必须返回一个实现了IEnumerator 接口的对象。除此以外,我们需要返回的这个对象不仅实现了IEnumerator,而且知道如何枚举ListBox对象。你将需要创建一个 ListBoxEmunerator(在下面描述):

NOTE:IEnumerable 和 IEnumerator 是不同的接口,请不要搞混了。

public IEnumerator GetEnumerator()

{

return new ListBoxEnumerator();

}

现在,ListBox 可以使用 foreach 循环了:

ListBox lbt = new ListBox("Hello", "World");

lbt.Add("Who");

lbt.Add("Is");

lbt.Add("John");

lbt.Add("Galt");

foreach (string s in lbt)

{

Console.WriteLine("Value: {0}", s);

}

先是实例化这个ListBox ,并初始了两个字符串,随后又添加了四个。foreach循环接受ListBox实例,并且迭代它,依次返回字符串。输出是:

Hello

World

Who

Is

John

Galt

实现 IEnumerator 接口注意到ListBoxEnumerator不仅需要实现IEnumerator接口,对于ListBox类它也需要一些特别了解;特别是,它必须可以获得ListBox的字符串数组并且遍历其所包含的字符串。IEnumerable 类和与其相关的 IEnumerator类之间的关系有一点微妙。实现IEnumerator接口的最好办法是在IEnumerable类里创建一个嵌套的IEnumerator类。

public class ListBox : IEnumerable

{

// 嵌套的私有ListBoxEnumerator类实现

private class ListBoxEnumerator : IEnumerator

{

// 代码实现...

}

// ListBox类的代码...

}

注意ListBoxEnumerator需要对它所嵌入的ListBox类的一个引用。你可以通过ListBoxEnumerator的构造函数来传递。

为了实现IEnumerator接口,ListBoxEnumerator需要两个方法:MoveNext和Reset,还有一个属性:Current。这些方法和属性的任务是创建一个状态机制,确保你可以在任何时候得知ListBox中的哪个元素是当前元素,并获得那个元素。

在这个例子中,这种状态机制是通过维护一个标明当前string的索引值来完成的,并且,你可以通过对外部类的string集合进行索引来返回这个当前的string。为了达到这个目标,你需要一个成员变量保存对于外部ListBox对象的引用,以及一个整型用于保存当前索引。

private ListBox lbt;

private int index;

每次Reset方法被调用的时候,index被置为 -1。

public void Reset()

{

index = -1;

}

每次MoveNext被调用的时候,外部类的数组检查时候已经到了末尾,如果是这样,方法返回false。如果集合中还有对象,index将增加,并且方法返回true。

public bool MoveNext()

{

index++;

if (index >= lbt.strings.Length)

{

return false;

}else

{

return true;

}

}

最后,如果MoveNext方法返回True,foreach循环将调用Current属性。ListBoxEnumerator的Current属性的实现是索引外部类(ListBox)中的集合,并且返回找到的对象(这个例子中,是一个字符串)。注意,返回一个Object是因为IEnumerator接口中Current属性的签名如此。

public object Current

{

get {

return(lbt[index]);

}

}

在1.1中,所有想要通过foreach循环来迭代的类都需要实现IEnumerable接口,于是,必须创建一个实现了IEnumerator的类。最糟的是,enumerator返回的值并不是类型安全的。记得Current属性返回一个Object对象;它仅仅简单的假设你所返回的值与foreach循环所期望的相符合。

C# 2.0 的解救办法使用C# 2.0 这些问题如同五月末的雪般融化了。在这个例子的2.0版本中,我重写上面的列表,使用C# 2.0的两个新特性:泛型 和 枚举器。

我以重新定义实现IEumerable<string>的ListBox作为开始:

public class ListBox : IEnumerable<string>

这样做确定这个类可以在foreach循环中使用,同时确保迭代的值是string类型。

现在,从上个例子中挪去整个嵌套类,并且用下面的代码替换 GetEnumerator方法。

public IEnumerator<string> GetEnumerator()

{

foreach (string s in strings)

{

yield return s;

}

}

GetEnumerator方法使用了新的 yield 语句。yield语句返回一个表达式。yield语句仅在迭代块中出现,并且返回foreach语句所期望的值。那也就是,对GetEnumerator的每次调用都将会产生集合中的下一个字符串;所有的状态管理已经都为你做好了!

就这样了,你已经完成了。不需要为每个类型实现你自己的enumerator,不需要创建嵌套类。你已经移除了至少30行代码,并且极大地简化了你的代码。程序继续像期望的那样运行,但是状态管理不再是你的任务,所有的都为你做好了。更进一步,由枚举器所返回的值一定是string类型,如果你想要返回其他类型,你可以修改IEnumerable泛型语句,IEnumerable泛型语句将反射新类型。

关于Yield的更多内容作为对上一节的一些说明,应该告诉你:实际上,你可以在yield语句块中yield一个以上的值。这样,下面的语句是完全正确的C#语句:

public IEnumerator GetEnumerator()

{

yield return "Who";

yield return " is";

yield return "John Galt?";

}

假设上面的代码位于一个名为foo的类中,你可以这样写:

foreach ( string s in new foo())

{

Console.Write(s);

}

输出结果将会是:

Who is John Galt?

如果你现在停下来思考一下,这些也是之前的代码所做的事。它遍历了自己的foreach循环,并且产生出它所找到的每个string字符串。

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
如何用java替换看不见的字符比如零宽空格&#8203;十六进制U+200B
 干货   2023-09-10
网页字号不能单数吗,网页字体大小为什么一般都是偶数
 干货   2023-09-06
java.lang.ArrayIndexOutOfBoundsException: 4096
 干货   2023-09-06
Noto Sans CJK SC字体下载地址
 干货   2023-08-30
window.navigator和navigator的区别是什么?
 干货   2023-08-23
js获取referer、useragent、浏览器语言
 干货   2023-08-23
oscache遇到404时会不会缓存?
 干货   2023-08-23
linux下用rm -rf *删除大量文件太慢怎么解决?
 干货   2023-08-08
刀郎新歌破世界纪录!
 娱乐   2023-08-01
js实现放大缩小页面
 干货   2023-07-31
生成式人工智能服务管理暂行办法
 百态   2023-07-31
英语学习:过去完成时The Past Perfect Tense举例说明
 干货   2023-07-31
Mysql常用sql命令语句整理
 干货   2023-07-30
科学家复活了46000年前的虫子
 探索   2023-07-29
英语学习:过去进行时The Past Continuous Tense举例说明
 干货   2023-07-28
meta name="applicable-device"告知页面适合哪种终端设备:PC端、移动端还是自适应
 干货   2023-07-28
只用css如何实现打字机特效?
 百态   2023-07-15
css怎么实现上下滚动
 干货   2023-06-28
canvas怎么画一个三角形?
 干货   2023-06-28
canvas怎么画一个椭圆形?
 干货   2023-06-28
canvas怎么画一个圆形?
 干货   2023-06-28
canvas怎么画一个正方形?
 干货   2023-06-28
中国河南省郑州市金水区蜘蛛爬虫ip大全
 干货   2023-06-22
javascript简易动态时间代码
 干货   2023-06-20
感谢员工的付出和激励的话怎么说?
 干货   2023-06-18
 
>>返回首页<<
 
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有