c#IEnumerable和IEnumeratorLambda表达式
初学C#的时候,⽼是被IEnumerable、IEnumerator、ICollection等这样的接⼝弄的糊⾥糊涂,我觉得有必要切底的弄清楚IEnumerable 和IEnumerator的本质。
下⾯我们先看IEnumerable和IEnumerator两个接⼝的语法定义。其实IEnumerable接⼝是⾮常的简单,只包含⼀个抽象的⽅法GetEnumerator(),它返回⼀个可⽤于循环访问集合的IEnumerator对象。IEnumerator对象有什么呢?它是⼀个真正的集合访问器,没有它,就不能使⽤foreach语句遍历集合或数组,因为只有IEnumerator对象才能访问集合中的项,假如连集合中的项都访问不了,那么进⾏集合的循环遍历是不可能的事情了。那么让我们看看IEnumerator接⼝有定义了什么东西。看下图我们知道IEnumerator接⼝定义了⼀个Current属性,MoveNext和Reset两个⽅法,这是多么的简约。既然IEnumerator对象时⼀个访问器,那⾄少应该有⼀个Current属性,来获取当前集合中的项吧。
MoveNext⽅法只是将游标的内部位置向前移动(就是移到⼀下个元素⽽已),要想进⾏循环遍历,不向前移动⼀下怎么⾏呢?
详细讲解:
说到IEnumerable总是会和IEnumerator、foreach联系在⼀起。
C# ⽀持关键字foreach,允许我们遍历任何数组类型的内容:
//遍历数组的项
int[] myArrayOfInts = {10,20,30,40};
foreach(int i in my myArrayOfInts)
{
Console.WirteLine(i);
}
虽然看上去只有数组才可以使⽤这个结构,其实任何⽀持GetEnumerator()⽅法的类型都可以通过foreach结构进⾏运算。
[csharp]
1. public class Garage
2. {
3. Car[] carArray = new Car[4]; //在Garage中定义⼀个Car类型的数组carArray,其实carArray在这⾥的本质是⼀个数组字段
4.
5. //启动时填充⼀些Car对象
6. public Garage()
7. {
8. //为数组字段赋值
9. carArray[0] = new Car("Rusty", 30);
10. carArray[1] = new Car("Clunker", 50);
11. carArray[2] = new Car("Zippy", 30);
12. carArray[3] = new Car("Fred", 45);
13. }
14. }
理想情况下,与数据值数组⼀样,使⽤foreach构造迭代Garage对象中的每⼀个⼦项⽐较⽅便:
[csharp]
1. //这看起来好像是可⾏的
2. lass Program
3. {
4. static void Main(string[] args)
5. {
6. Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n");
7. Garage carLot = new Garage();
8.
9. //交出集合中的每⼀Car对象吗
10. foreach (Car c in carLot)
11. {lambda编程
12. Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed);
13. }
14.
15. Console.ReadLine();
16. }
17. }
让⼈沮丧的是,编译器通知我们Garage类没有实现名为GetEnumerator()的⽅法(显然⽤foreach遍历Garage对象是不可能的事情,因为Garage类没有实现GetEnumerator()⽅法,Garage对象就不可能返回⼀个IEnumerator对象,没有IEnumerator对象,就不可能调⽤⽅法MoveNext(),调⽤不了MoveNext,
就不可能循环的了)。这个⽅法是有隐藏在llections命名空间中的IEnumerable接⼝定义的。(特别注意,其实我们循环遍历的都是对象⽽不是类,只是这个对象是⼀个集合对象)
⽀持这种⾏为的类或结构实际上是宣告它们向调⽤者公开所包含的⼦项:
//这个接⼝告知调⽅对象的⼦项可以枚举
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
可以看到,GetEnumerator⽅法返回对另⼀个接⼝System.Collections.IEnumerator的引⽤。这个接⼝提供了基础设施,调⽤⽅可以⽤来移动IEnumerable兼容容器包含的内部对象。
//这个接⼝允许调⽤⽅获取⼀个容器的⼦项
public interface IEnumerator
{
bool MoveNext(); //将游标的内部位置向前移动
object Current{get;} //获取当前的项(只读属性)
void Reset(); //将游标重置到第⼀个成员前⾯
}
所以,要想Garage类也可以使⽤foreach遍历其中的项,那我们就要修改Garage类型使之⽀持这些接⼝,可以⼿⼯实现每⼀个⽅法,不过这得花费不少功夫。虽然⾃⼰开发GetEnumerator()、MoveNext()、Current和Reset()也没有问题,但有⼀个更简单的办法。因为System.Array类型和其他许多类型(如List)已经实现了IEnumerable和IEnumerator接⼝,你可以简单委托请求到System.Array,如下所⽰:
[csharp]
1. namespace MyCarIEnumerator
2. {
3. public class Garage:IEnumerable
4. {
5. Car[] carArray = new Car[4];
6.
7. //启动时填充⼀些Car对象
8. public Garage()
9. {
10. carArray[0] = new Car("Rusty", 30);
11. carArray[1] = new Car("Clunker", 50);
12. carArray[2] = new Car("Zippy", 30);
13. carArray[3] = new Car("Fred", 45);
14. }
15. public IEnumerator GetEnumerator()
16. {
17. return this.carArray.GetEnumerator();
18. }
19. }
20. }
21. //修改Garage类型之后,就可以在C#foreach结构中安全使⽤该类型了。
[csharp]
1. //除此之外,GetEnumerator()被定义为公开的,对象⽤户可以与IEnumerator类型交互:
2. namespace MyCarIEnumerator
3. {
4. class Program
5. {
6. static void Main(string[] args)
7. {
8. Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n");
9. Garage carLot = new Garage();
10.
11. //交出集合中的每⼀Car对象吗
12. foreach (Car c in carLot) //之所以遍历carLot,是因为carLot.GetEnumerator()返回的项时Car类型,这个⼗分重要
13. {
14. Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed);
15. }
16.
17. Console.WriteLine("GetEnumerator被定义为公开的,对象⽤户可以与IEnumerator类型交互,下⾯的结果与上⾯是⼀致
的");
18. //⼿动与IEnumerator协作
19. IEnumerator i = carLot.GetEnumerator();
20. while (i.MoveNext())
21. {
22. Car myCar = (Car)i.Current;
23. Console.WriteLine("{0} is going {1} MPH", myCar.CarName, myCar.CurrentSpeed);
24. }
25. Console.ReadLine();
26. }
27. }
28. }
下⾯我们来看看⼿⼯实现IEnumberable接⼝和IEnumerator接⼝中的⽅法:
[csharp]
1. namespace ForeachTestCase
2. {
3. //继承IEnumerable接⼝,其实也可以不继承这个接⼝,只要类⾥⾯含有返回IEnumberator引⽤的GetEnumerator()⽅法即可
4. class ForeachTest:IEnumerable {
5. private string[] elements; //装载字符串的数组
6. private int ctr = 0; //数组的下标计数器
7.
8. /// <summary>
9. /// 初始化的字符串
10. /// </summary>
10. /// </summary>
11. /// <param name="initialStrings"></param>
12. ForeachTest(params string[] initialStrings)
13. {
14. //为字符串分配内存空间
15. elements = new String[8];
16. //复制传递给构造⽅法的字符串
17. foreach (string s in initialStrings)
18. {
19. elements[ctr++] = s;
20. }
21. }
22.
23. /// <summary>
24. /// 构造函数
25. /// </summary>
26. /// <param name="source">初始化的字符串</param>
27. /// <param name="delimiters">分隔符,可以是⼀个或多个字符分隔</param>
28. ForeachTest(string initialStrings, char[] delimiters)
29. {
30. elements = initialStrings.Split(delimiters);
31. }
32.
33. //实现接⼝中得⽅法
34. public IEnumerator GetEnumerator()
35. {
36. return new ForeachTestEnumerator(this);
37. }
38.
39. private class ForeachTestEnumerator : IEnumerator
40. {
41. private int position = -1;
42. private ForeachTest t;
43. public ForeachTestEnumerator(ForeachTest t)
44. {
45. this.t = t;
46. }
47.
48. #region 实现接⼝
49.
50. public object Current
51. {
52. get
53. {
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论