.net 接口与 foreach必要条件

引用自http://blog.csdn.net/byondocean/article/details/6871881

初学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] view plaincopy

  1. public class Garage

  2. {

  3. Car[] carArray = new Car[4];  //在Garage中定义一个Car类型的数组carArray,其实carArray在这里的本质是一个数组字段
  4. //启动时填充一些Car对象

  5. public Garage()

  6. {

  7. //为数组字段赋值

  8. carArray[0] = new Car("Rusty", 30);

  9. carArray[1] = new Car("Clunker", 50);

  10. carArray[2] = new Car("Zippy", 30);

  11. carArray[3] = new Car("Fred", 45);

  12. }

  13. }

理想情况下,与数据值数组一样,使用foreach构造迭代Garage对象中的每一个子项比较方便:

[csharp] view plaincopy

  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. //交出集合中的每一Car对象吗

  9. foreach (Car c in carLot)

  10. {

  11. Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed);

  12. }
  13. Console.ReadLine();

  14. }

  15. }

让人沮丧的是,编译器通知我们Garage类没有实现名为GetEnumerator()的方法(显然用foreach遍历Garage对象
不可能的事情,因为Garage类没有实现GetEnumerator()方法,Garage对象就不可能返回一个IEnumerator对象,没有
IEnumerator对象,就不可能调用方法MoveNext(),调用不了MoveNext,就不可能循环的了)。这个方法是有隐藏在
System.collections命名空间中的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] view plaincopy

  1. namespace MyCarIEnumerator

  2. {

  3. public class Garage:IEnumerable

  4. {

  5. Car[] carArray = new Car[4];
  6. //启动时填充一些Car对象

  7. public Garage()

  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. public IEnumerator GetEnumerator()

  15. {

  16. return this.carArray.GetEnumerator();

  17. }

  18. }

  19. }

  20. //修改Garage类型之后,就可以在C#foreach结构中安全使用该类型了。

[csharp] view plaincopy

  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. //交出集合中的每一Car对象吗

  11. foreach (Car c in carLot)  //之所以遍历carLot,是因为carLot.GetEnumerator()返回的项时Car类型,这个十分重要

  12. {

  13. Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed);

  14. }
  15. Console.WriteLine("GetEnumerator被定义为公开的,对象用户可以与IEnumerator类型交互,下面的结果与上面是一致的");

  16. //手动与IEnumerator协作

  17. IEnumerator i = carLot.GetEnumerator();

  18. while (i.MoveNext())

  19. {

  20. Car myCar = (Car)i.Current;

  21. Console.WriteLine("{0} is going {1} MPH", myCar.CarName, myCar.CurrentSpeed);

  22. }

  23. Console.ReadLine();

  24. }

  25. }

  26. }

下面我们来看看手工实现IEnumberable接口和IEnumerator接口中的方法:

[csharp] view plaincopy

  1. namespace ForeachTestCase

  2. {

  3. //继承IEnumerable接口,其实也可以不继承这个接口,只要类里面含有返回IEnumberator引用的GetEnumerator()方法即可

  4. class ForeachTest:IEnumerable     {

  5. private string[] elements;  //装载字符串的数组

  6. private int ctr = 0;  //数组的下标计数器
  7. /// <summary>

  8. /// 初始化的字符串

  9. /// </summary>

  10. /// <param name="initialStrings"></param>

  11. ForeachTest(params string[] initialStrings)

  12. {

  13. //为字符串分配内存空间

  14. elements = new String[8];

  15. //复制传递给构造方法的字符串

  16. foreach (string s in initialStrings)

  17. {

  18. elements[ctr++] = s;

  19. }

  20. }
  21. /// <summary>

  22. ///  构造函数

  23. /// </summary>

  24. /// <param name="source">初始化的字符串</param>

  25. /// <param name="delimiters">分隔符,可以是一个或多个字符分隔</param>

  26. ForeachTest(string initialStrings, char[] delimiters)

  27. {

  28. elements = initialStrings.Split(delimiters);

  29. }
  30. //实现接口中得方法

  31. public IEnumerator GetEnumerator()

  32. {

  33. return  new ForeachTestEnumerator(this);

  34. }
  35. private class ForeachTestEnumerator : IEnumerator

  36. {

  37. private int position = -1;

  38. private ForeachTest t;

  39. public ForeachTestEnumerator(ForeachTest t)

  40. {

  41. this.t = t;

  42. }
  43. #region 实现接口
  44. public object Current

  45. {

  46. get

  47. {

  48. return t.elements[position];

  49. }

  50. }
  51. public bool MoveNext()

  52. {

  53. if (position < t.elements.Length - 1)

  54. {

  55. position++;

  56. return true;

  57. }

  58. else

  59. {

  60. return false;

  61. }

  62. }
  63. public void Reset()

  64. {

  65. position = -1;

  66. }
  67. #endregion

  68. }

  69. static void Main(string[] args)

  70. {

  71. // ForeachTest f = new ForeachTest("This is a sample sentence.", new char[] { ‘ ‘, ‘-‘ });

  72. ForeachTest f = new ForeachTest("This", "is", "a", "sample", "sentence.");

  73. foreach (string item in f)

  74. {

  75. System.Console.WriteLine(item);

  76. }

  77. Console.ReadKey();

  78. }

  79. }

  80. }

IEnumerable<T>接口

实现了IEnmerable<T>接口的集合,是强类型的。它为子对象的迭代提供类型更加安全的方式。

[csharp] view plaincopy

  1. public  class ListBoxTest:IEnumerable<String>

  2. {

  3. private string[] strings;

  4. private int ctr = 0;
  5. #region IEnumerable<string> 成员

  6. //可枚举的类可以返回枚举

  7. public IEnumerator<string> GetEnumerator()

  8. {

  9. foreach (string s in strings)

  10. {

  11. yield return s;

  12. }

  13. }
  14. #endregion
  15. #region IEnumerable 成员

  16. //显式实现接口

  17. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

  18. {

  19. return GetEnumerator();

  20. }
  21. #endregion
  22. //用字符串初始化列表框

  23. public ListBoxTest(params string[] initialStrings)

  24. {

  25. //为字符串分配内存空间

  26. strings = new String[8];

  27. //复制传递给构造方法的字符串

  28. foreach (string s in initialStrings)

  29. {

  30. strings[ctr++] = s;

  31. }

  32. }
  33. //在列表框最后添加一个字符串

  34. public void Add(string theString)

  35. {

  36. strings[ctr] = theString;

  37. ctr++;

  38. }
  39. //允许数组式的访问

  40. public string this[int index]

  41. {

  42. get {

  43. if (index < 0 || index >= strings.Length)

  44. {

  45. //处理不良索引

  46. }

  47. return strings[index];

  48. }

  49. set {

  50. strings[index] = value;

  51. }

  52. }
  53. //发布拥有的字符串数

  54. public int GetNumEntries()

  55. {

  56. return ctr;

  57. }

  58. }

[csharp] view plaincopy

  1. class Program

  2. {

  3. static void Main(string[] args)

  4. {

  5. //创建一个新的列表框并初始化

  6. ListBoxTest lbt = new ListBoxTest("Hello", "World");
  7. //添加新的字符串

  8. lbt.Add("Who");

  9. lbt.Add("Is");

  10. lbt.Add("Douglas");

  11. lbt.Add("Adams");
  12. //测试访问

  13. string subst = "Universe";

  14. lbt[1] = subst;
  15. //访问所有的字符串

  16. foreach (string s in lbt)

  17. {

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

  19. }

  20. Console.ReadKey();

  21. }

  22. }

综上所述,一个类型是否支持foreach遍历,必须满足下面条件:

方案1:让这个类实现IEnumerable接口

方案2:这个类有一个public的GetEnumerator的实例方法,并且返回类型中有public 的bool
MoveNext()实例方法和public的Current实例属性。

.net 接口与 foreach必要条件,布布扣,bubuko.com

时间: 2024-10-16 16:13:24

.net 接口与 foreach必要条件的相关文章

疯狂Java学习笔记(34)----------Iterator、Collection接口以及foreach

Iterator.Collection接口: 如下图:Iterator.Collection同在一个包中: 红字部分使我们经常遇到的,但是遇到又不知道怎么去理解,去应用它! Collection是最基本集合接口,它定义了一组允许重复的对象.Collection接口派生了两个子接口Set和List,分别定义了两种不同的存储方式,如下: 2. Set接口 Set接口继承于Collection接口,它没有提供额外的方法,但实现了Set接口的集合类中的元素是无序且不可重复. 特征:无序且不可重复. 3.

C#中的IEnumable与IEnumator接口的简单理解

IEnumerable接口中的方法是返回IEnumator的对象,集合继承了IEnumerator接口才能实现Foreach方法实现遍历.集合类都继承IEnumable和IEnumerator接口,或者说是这两个接口提供foreach遍历的功能. 综上所述,一个类是否支持foreach遍历,必须满足下面条件: 方案1:让这个类实现IEnumerable接口 方案2:这个类有一个public的GetEnumerator的实例方法,并且返回类型中有public 的bool MoveNext()实例方

Foreach与迭代器

Foreach语句可以用于数组和集合的遍历.之所以能够工作,是因为Java SE5引入了新的被称为Iterable的接口,该接口中包含一个能够产生Iterator的iterator()方法,并且Iterable接口被foreach用来在序列中移动.因此如果你创建了任何实现Iterable的类,都可以将它用于foreach语句中. ——Java编程思想第4版 第11章 243页 1.任何实现了Iterable接口的类都可以用于foreach语句当中. 1 //: holding/IterableC

数据持久化、单例、重载【添加对不可访问的成员的操作】、魔术方法、类常量、static关键字对self的补充【静态延迟绑定实现$this的效果】、参数类型约束【参数前加类名】、遍历【iterator接口】、快速排序

1.数据持久化过程[传输(例如表单提交或php交互mysql)和保存过程] 使用的是字符串形式的流数据. 数据流就是为了传输[按照序列的形式进行传输] [http://baike.baidu.com/link?url=0MtUQMhFzc_EwJc09rXZV8KlfOL4jis6XNbRfmGA3rQhDcGwOp8togLVQjXBV34M] 所以将其他类型数据转化为字符串的过程也是序列化的过程 [这个概念和图片.视频的流媒体的区别?] [注意点] 另外mysql中sql语句中的某些关键词为

C#编程(三十五)----------foreach和yield

枚举 在foreach语句中使用枚举,可以迭代集合中的元素,且无需知道集合中的元素个数. 数组或集合实现带GetEumerator()方法的IEumerable接口.GetEumerator()方法返回一个实现IEunmerable接口的枚举. GetEnumerator()方法用IEnumerable接口定义.foreach语句并不真的需要在集合类中实现这个借口.有一个名为GetEnumerator()的方法,他返回实现了IEnumerator接口的对象就足够了. IEnumerator接口

集合接口和类

1.综述 所有集合类都位于java.util包下.集合中只能保存对象(保存对象的引用变量).(数组既可以保存基本类型的数据也可以保存对象). 当我们把一个对象放入集合中后,系统会把所有集合元素都当成Object类的实例进行处理.从JDK1.5以后,这种状态得到了改进:可以使用泛型来限制集合里元素的类型,并让集合记住所有集合元素的类型(参见具体泛型的内容). Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包

C#高级编程三十五天----foreach和yield

枚举 在foreach语句中使用枚举,可以迭代集合中的元素,且无需知道集合中的元素个数. 数组或集合实现带GetEumerator()方法的IEumerable接口.GetEumerator()方法返回一个实现IEunmerable接口的枚举. GetEnumerator()方法用IEnumerable接口定义.foreach语句并不真的需要在集合类中实现这个借口.有一个名为GetEnumerator()的方法,他返回实现了IEnumerator接口的对象就足够了. IEnumerator接口

foreach的使用原理简单解析

数组可以foreach遍历,这个是在jdk1.5之前就可以的,我也不太清楚是怎么做到的. 后面的List,Set等的foreach都是实现Iterable接口,基于iterator()对象实现的.Foreach之所以能工作,是因为Java SE5引入了新的被称为Iterable的接口,该接口包含一个能够产生Iterator的iterator()方法,并且Iterable接口被foreach用来在序列中移动.因此如果你创建了任何实现Iterator的类,都可以将它用于forEach语句中. pub

乐字节-Java8核心特性实战之函数式接口

什么时候可以使用Lambda?通常Lambda表达式是用在函数式接口上使用的.从Java8开始引入了函数式接口,其说明比较简单:函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口. java8引入@FunctionalInterface 注解声明该接口是一个函数式接口. 语法 抽象方法有且仅有一个 接口使用@FunctionalInterface 注解进行标注 接口中可以存在默认方法和静态方法实现 如下形式: /** * 定义函数式