IEnumerable和IEnumerator 详解 (转)

原文链接:http://blog.csdn.net/byondocean/article/details/6871881

参考链接:http://www.cnblogs.com/hsapphire/archive/2010/04/16/1713211.html

 

初学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.   
  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] 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.   
  9.            //交出集合中的每一Car对象吗  
  10.             foreach (Car c in carLot)  
  11.            {  
  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,就不可能循环的了)。这个方法是有隐藏在 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.   
  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] 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.   
  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] view plaincopy

  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>  
  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.                 {  
  54.                     return t.elements[position];  
  55.                 }  
  56.             }  
  57.   
  58.             public bool MoveNext()  
  59.             {  
  60.                 if (position < t.elements.Length - 1)  
  61.                 {  
  62.                     position++;  
  63.                     return true;  
  64.                 }  
  65.                 else  
  66.                 {  
  67.                     return false;  
  68.                 }  
  69.             }  
  70.   
  71.             public void Reset()  
  72.             {  
  73.                 position = -1;  
  74.             }  
  75.  
  76.             #endregion  
  77.         }  
  78.         static void Main(string[] args)  
  79.         {  
  80.             // ForeachTest f = new ForeachTest("This is a sample sentence.", new char[] { ' ', '-' });  
  81.             ForeachTest f = new ForeachTest("This", "is", "a", "sample", "sentence.");  
  82.             foreach (string item in f)  
  83.             {  
  84.                 System.Console.WriteLine(item);  
  85.             }  
  86.             Console.ReadKey();  
  87.         }  
  88.     }  
  89. }  

 

IEnumerable<T>接口

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

[csharp] view plaincopy

  1. public  class ListBoxTest:IEnumerable<String>  
  2.    {  
  3.        private string[] strings;  
  4.        private int ctr = 0;  
  5.       
  6.        #region IEnumerable<string> 成员  
  7.        //可枚举的类可以返回枚举  
  8.        public IEnumerator<string> GetEnumerator()  
  9.        {  
  10.            foreach (string s in strings)  
  11.            {  
  12.                yield return s;  
  13.            }  
  14.        }  
  15.  
  16.        #endregion  
  17.  
  18.        #region IEnumerable 成员  
  19.        //显式实现接口  
  20.        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()  
  21.        {  
  22.            return GetEnumerator();  
  23.        }  
  24.  
  25.        #endregion  
  26.   
  27.        //用字符串初始化列表框  
  28.        public ListBoxTest(params string[] initialStrings)  
  29.        {   
  30.            //为字符串分配内存空间  
  31.            strings = new String[8];  
  32.            //复制传递给构造方法的字符串  
  33.            foreach (string s in initialStrings)  
  34.            {  
  35.                strings[ctr++] = s;   
  36.            }  
  37.        }  
  38.   
  39.        //在列表框最后添加一个字符串  
  40.        public void Add(string theString)  
  41.        {   
  42.            strings[ctr] = theString;  
  43.            ctr++;  
  44.        }  
  45.   
  46.        //允许数组式的访问  
  47.        public string this[int index]  
  48.        {  
  49.            get {  
  50.                if (index < 0 || index >= strings.Length)  
  51.                {   
  52.                    //处理不良索引  
  53.                }  
  54.                return strings[index];  
  55.            }  
  56.            set {   
  57.                strings[index] = value;  
  58.            }  
  59.        }  
  60.   
  61.        //发布拥有的字符串数  
  62.        public int GetNumEntries()  
  63.        {  
  64.            return ctr;  
  65.        }  
  66.    }  

[csharp] view plaincopy

  1. class Program  
  2.   {  
  3.       static void Main(string[] args)  
  4.       {  
  5.           //创建一个新的列表框并初始化  
  6.           ListBoxTest lbt = new ListBoxTest("Hello", "World");  
  7.   
  8.           //添加新的字符串  
  9.           lbt.Add("Who");  
  10.           lbt.Add("Is");  
  11.           lbt.Add("Douglas");  
  12.           lbt.Add("Adams");  
  13.   
  14.           //测试访问  
  15.           string subst = "Universe";  
  16.           lbt[1] = subst;  
  17.   
  18.           //访问所有的字符串  
  19.           foreach (string s in lbt)  
  20.           {  
  21.               Console.WriteLine("Value:{0}", s);  
  22.           }  
  23.           Console.ReadKey();  
  24.       }  
  25.   }  

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

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

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

 

时间: 2024-10-27 11:13:06

IEnumerable和IEnumerator 详解 (转)的相关文章

IEnumerable 使用foreach 详解

自己实现迭代器 yield的使用 怎样高性能的随机取IEnumerable中的值 我们先思考几个问题: 为什么在foreach中不能修改item的值? 要实现foreach需要满足什么条件? 为什么Linq to Object中要返回IEnumerable? 接下来,先开始我们的正文. 自己实现迭代器 .net中迭代器是通过IEnumerable和IEnumerator接口来实现的,今天我们也来依葫芦画瓢. 首先来看看这两个接口的定义: 并没有想象的那么复杂.其中IEnumerable只有一个返

.NET深入解析LINQ框架(五:IQueryable、IQueryProvider接口详解)

阅读目录: 1.环路执行对象模型.碎片化执行模型(假递归式调用) 2.N层对象执行模型(纵横向对比链式扩展方法) 3.LINQ查询表达式和链式查询方法其实都是空壳子 4.详细的对象结构图(对象的执行原理) 5.IQueryable<T>与IQueryProvider一对一的关系能否改成一对多的关系 6.完整的自定义查询 1]. 环路执行对象模型.碎片化执行模型(假递归式调用) 这个主题扯的可能有点远,但是它关系着整个LINQ框架的设计结构,至少在我还没有搞懂LINQ的本意之前,在我脑海里一直频

必知技巧:ASP.NET中常用的优化性能方法详解

asp.net|技巧|详解|性能|优化 1. 数据库访问性能优化 数据库的连接和关闭 访问数据库资源需要创建连接.打开连接和关闭连接几个操作.这些过程需要多次与数据库交换信息以通过身份验证,比较耗费服务器资源.ASP.NET中提供了连接池(Connection Pool)改善打开和关闭数据库对性能的影响.系统将用户的数据库连接放在连接池中,需要时取出,关闭时收回连接,等待下一次的连接请求. 连接池的大小是有限的,如果在连接池达到最大限度后仍要求创建连接,必然大大影响性能.因此,在建立数据库连接后

MVC系列(7) WebActivator的实现原理详解

上篇文章,我们分析如何动态注册HttpModule的实现,本篇我们来分析一下通过上篇代码原理实现的WebActivator类库,WebActivator提供了3种功能,允许我们分别在HttpApplication初始化之前,之后以及ShutDown的时候分别执行指定的代码,示例如下: [assembly: WebActivator.PreApplicationStartMethod(typeof(A.InitClass1), "PreStart")] [assembly: WebAct

ASP.NET MVC Controller激活系统详解:IoC的应用[下篇]

[上篇]除了通过自定义ControllerFactory的方式引入IoC之外,在使用默认DefaultControllerFactory情况下也可以通过一些扩展使基于IoC的Controller激活成为可能.主要的方式就是自定义ControllerActivator和 DependencyResolver. 四.ControllerActivator V.S. DependencyResolver 如下面的代码片断所示,DefaultControllerFactory具有两个构造函数重载,其中一

ASP.NET MVC Controller激活系统详解:默认实现

Controller激活系统最终通过注册的ControllerFactory创建相应的Conroller对象,如果没有对ControllerFactory类型或者类型进行显式注册(通过调用当前ControllerBuilder的SetControllerFactory方法),默认使用的是一个DefaultControllerFactory对象,我们现在就来讨论实现在DefaultControllerFactory类型中的默认Controller激活机制. 一.Controller类型的解析 激活

WCF技术剖析之十七:消息(Message)详解(下篇)

<WCF技术剖析(卷1)>自出版近20天以来,得到了园子里的朋友和广大WCF爱好者的一致好评,并被卓越网计算机书店作为首页推荐,在这里对大家的支持表示感谢.同时我将一直坚持这个博文系列,与大家分享我对WCF一些感悟和学习经验.在<消息(Message)详解>系列的上篇和中篇,先后对消息版本.详细创建.状态机和基于消息的基本操作(读取.写入.拷贝.关闭)进行了深入剖析,接下来我们来谈谈消息的另一个重要组成部分:消息报头(Message Header). 按照SOAP1.1或者SOAP

.Net开发必备十大工具详解之查询表达式(LINQ)

LINQ是Language Integrated Query的简称,它是集成在.NET编程语言中的一种特性.已成为编程语言的一个组成部分,在编写程序时可以得到很好的编译时语法检查,丰富的元数据,智能感知.静态类型等强类型语言的好处.并且它同时还使得查询可以方便地对内存中的信息进行查询而不仅仅只是外部数据源. LINQ定义了一组标准查询操作符用于在所有基于.NET平台的编程语言中更加直接地声明跨越.过滤和投射操作的统一方式,标准查询操作符允许查询作用于所有基于IEnumerable<T>接口的源

Memcached常用命令以及使用说明详解

一.存储命令 存储命令的格式: ? 1 2 <command name> <key> <flags> <exptime> <bytes> <data block> 参数说明如下: <command name> set/add/replace <key> 查找关键字 <flags> 客户机使用它存储关于键值对的额外信息 <exptime> 该数据的存活时间,0表示永远 <bytes&