IEnumerable和IEnumerator 详解

2023-11-04

初学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 plain  copy
  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 plain  copy
  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 plain  copy
  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 plain  copy
  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 plain  copy
  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 plain  copy
  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 plain  copy
  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实例属性。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

IEnumerable和IEnumerator 详解 的相关文章

  • .crt 部分?这个警告是什么意思?

    我最近收到此警告 VC 2010 warning LNK4210 CRT section exists there may be unhandled static initializers or terminators 我假设这是关键部分
  • 自动映射器多对多 stackoverflowException

    我遇到以下映射的堆栈溢出 Mapper CreateMap
  • 深拷贝和动态转换 unique_ptr

    假设我有一个如下所示的类 class A virtual A class B public A class C public A 我还有一个 unique ptr 向量 它是这样声明的 std vector
  • 如何在C中同时运行两个子进程?

    所以我开始学习并发编程 但由于某种原因我什至无法掌握基础知识 我有一个名为 fork c 的文件 其中包含一个 main 方法 在此方法中 我将 main 分叉两次 分别进入子进程 1 和 2 在孩子 1 中 我打印了字符 A 50 次 在
  • ContentDialog 未与 UWP 中心对齐

    据我所知 ContentDialog的默认行为应该是使其在 PC 上居中并在移动设备上与顶部对齐 但就我而言 即使在 PC 上我也将其与顶部对齐 但我不明白发生了什么 我正在使用代码隐藏来创建它 这是我正在使用的代码片段 Creates t
  • 将 dataGridView 中选定的行作为对象检索

    我有一堂这样的课 public partial class AdressBokPerson public long Session get set public string F rnamn get set public string Ef
  • Linq 合并列表

    我的课 public class Foo public int A get set public List
  • Visual Studio 中列表框的上移、下移按钮[重复]

    这个问题在这里已经有答案了 我正在尝试制作一个上移按钮和一个下移按钮 以移动 Microsoft Visual Studio 2012 中列表框中的选定项目 我已经在 WDF jquery winforms 和其他一些表单中看到了其他示例
  • 如何将 Q 格式整数转换为浮点数(反之亦然)?

    我四处搜寻 找不到一个很好的问题来回答这个问题 给定一个整数 使用Q Format https en wikipedia org wiki Q number format 如何将该数字转换为普通浮点类型 反之亦然 如何将浮点类型转换为Q F
  • C# 枚举到字符串自动转换?

    是否可以让编译器自动将我的 Enum 值转换为字符串 这样我就可以避免每次都显式调用 ToString 方法 这是我想做的一个例子 enum Rank A B C Rank myRank Rank A string myString Ran
  • 我在使用 ado.net 时收到错误 Argument 2 may not be pass with ref keywords

    int t 0 cmd Parameters AddWithValue Res ref t 我在第二行收到错误 参数 2 不能与 ref 关键字一起传递 您只能通过引用传递参数ref if the 范围 is a ref参数也是如此 Add
  • 更改 Xamarin.Forms 应用中顶部栏和底部栏(ControlsBar、StatusBar)的颜色

    无论如何 即使后面需要特定于平台的代码 也可以更改顶部栏 蓝色的 和底部栏 黑色的 的颜色吗 我希望添加对浅色和深色模式的支持 因此我希望能够在运行时更改它 有可能的 Android Using Window SetStatusBarCol
  • 在 C# 中使用命名空间别名有什么好处? [复制]

    这个问题在这里已经有答案了 使用命名空间别名有什么好处 仅仅是为了简化编码吗 仅当与类发生冲突时我才使用名称空间别名 对我来说 这根本没有简化 我的意见是 如果没有必要 就不要使用
  • 当需要不同数量和类型的参数时如何创建操作委托列表

    我们有一组大约两打的类 它们继承自具有抽象 Validate 方法的基类 当然 每个类都有不同的验证需求 但它们之间的不同组合需要规则 因此 正如您可以想象的那样 这导致了大量代码重复 例如 A 类需要规则 1 3 6 和 9B 类需要规则
  • valgrind 在 Raspberry Pi 上返回未处理的指令

    我最近一直在尝试在运行 Debian GNU Linux7 0 喘息 的树莓派 型号 b 上使用 valgrind 来调试分段错误 每次我在编译的 C 程序上运行 valgrind 时 都会得到类似以下内容的信息 disInstr arm
  • 选择合适的IDE

    您会推荐使用以下哪种 IDE 语言来在 Windows 下开发涉及识别手势并与操作系统交互的项目 我将使用 OpenCV 库来执行图像处理任务 之后 我将使用 win32 API 或 NET 框架与操作系统交互 具体取决于您建议的工具 性能
  • 当我的进程被终止时到底会发生什么?

    我有一个包含本机代码和托管代码的混合进程 在 Windows Server 2003 上运行 当我从进程资源管理器中终止进程时 它会进入 100 cpu 的状态 并在消失之前保持这种状态一段时间 有时甚至 10 分钟 在此期间我无法 杀死
  • 从对列表创建邻接列表类型结构

    在 C 中 我有 class Pair int val1 int val2 我有一个来自以下来源的配对列表 List
  • 在windows + opengl中选择图形设备

    我知道如何使用 openGL 打开窗口 使用 Win32 或其他工具包 但是当系统有2块显卡时 如何选择要渲染的图形设备 我的编程语言是 C 我专注于 Windows 但任何示例都将受到欢迎 编辑 也许更好地解释我的问题是个好主意 以便添加
  • 将一个 IEnumerable 拆分为多个 IEnumerable

    我是 linq 新手 我需要根据指示器将 Couple string text bool Indicator 类型的 IEnumerable 拆分为多个 IEnumerable 我尝试使用skipWhile 和 TakeWhile 但没有找

随机推荐

  • java集合的copy

    java拷贝集合的方法有很多种 常用的比较简单的做法有两种 直接使用集合构造方法实现浅拷贝 这种方法只是保证list和listCopy的引用不一样 但是集合元素的引用时一样的 List
  • 生产管理MES系统框架

    1 总体框架描述 生产管理MES系统框架涵盖了涉及生产制造范畴内的所有业务管理内容 包括 产品生产数据 销售订单管理 材料需求计算和计划 采购管理 仓库物流管理 主生产计划 生产作业管理 生产过程物料加工 生产过程工装组装管理 品质管理 检
  • idea 插件的使用 进阶篇(个人收集使用中的)

    idea 插件的使用 进阶篇 个人收集使用中的 恭喜你 如果你已经看到这篇文章 证明在idear使用上已经初有小成 那么就要向着大神进发了 下边就是大神之路 插件的设置 在 IntelliJ IDEA 的安装讲解中我们其实已经知道 Inte
  • Quartz和Spring Task定时任务的简单应用和比较

    一 Quartz 引入quartz的jar包 配置文件中定义org springframework scheduling quartz MethodInvokingJobDetailFactoryBean 并指定它的targetObject
  • 理解HTML、CSS、javascript之间的关系

    理解HTML CSS javascript之间的关系 版权属于 博客园 牧云流 本文作者 牧云流 原文链接 https www cnblogs com dreamingbaobei p 10407626 html 网页主要有三部分组成 结构
  • pygame从入门到放弃(一)

    首先pip 那个pygame 然后看代码 临时写的 图片就不插了 防止舍友砍我 现在是凌晨 TOC 井字棋游戏 此代码基本能立于不败之地 import random 可视化输出 def draw Board board print prin
  • gcc中预定义的宏__GNUC__ __GNUC_MINOR__ __GNUC_PATCHLEVEL__

    今天在看Linux系统编程这本书的代码的时候看到了 GNUC 不太清楚这个宏所以去查了一下 以此记录 GNU C预定义了一系列的宏 这些宏都是以双下划线开始的 这里只讲一下 GNUC GNUC MINOR GNUC PATCHLEVEL 完
  • vue中this.$set()的用法

    1 this set 的作用 向响应式对象中添加一个属性 并确保这个新属性同样是响应式的 且触发视图更新 this set 用于向响应式对象上添加新属性 因为 Vue 无法探测普通的新增属性 简单来说 就是我们在methods中给数据添加了
  • 整数拆分--

    题目描述 一个整数总可以拆分为2的幂的和 例如 7 1 2 4 7 1 2 2 2 7 1 1 1 4 7 1 1 1 2 2 7 1 1 1 1 1 2 7 1 1 1 1 1 1 1 总共有六种不同的拆分方式 再比如 4可以拆分成 4
  • [每日两题系列]刷算法题咯~~

    今日题目 最小栈 有效的括号 本系列所选题目均来自力扣或者牛客网站 所选题目主要是以其中的简单题为主 中等题为辅 包含少数困难题 原因是 本人目前能力还不够 开展这个系列的目的是督促自己 在暑假的时间里也要保持有一定的刷题量 拒绝摆烂 话不
  • FISCO BCOS 联盟链Max搭建

    FISCO BCOS Max版本 版本说明 为了能够支撑海量交易上链场景 v3 0 0推出了Max版本FISCO BCOS Max版本FISCO BCOS旨在提供海量存储服务 高性能可扩展的执行模块 高可用的故障恢复机制 Max版FISCO
  • ZYNQ #2 - Linux环境下烧录BOOT.BIN从QSPI-FLASH启动

    这篇博文讲述的是在Linux环境下 将生成的新BOOT BIN利用dd指令写入板上qspi flash中 板子从flash启动后 转至SD卡执行linux内核 这篇博文是为了之后不使用SD卡 将linux内核以及根文件系统放入emmc启动做
  • Web前端复习——Javascript(字符串)

    1 什么是字符串 字符串是多个字符组成的一个 只读 的集合 数组 注意 1 凡是数组对象中 不修改原对象的API 字符串都能用 比如 length属性 字符个数 用 i 访问每个字符 slice indexof 2 凡是数组对象中 直接修改
  • DocArray 和 Redis 联手,让推荐系统飞起来

    在DocArray中使用Redis后端 基于向量相似性搜索可以快速搭建一个实时商品推荐系统 现在 跟上我们的脚步 一起了解搭建系统的关键步骤 并且深入了解推荐的原理吧 推荐系统会根据用户画像 历史行为 如购买 喜欢 浏览等 给用户的兴趣建模
  • 36.求解简单的四则运算表达式,输入一个形式如“操作数  运算符  操作数”的四则运算表达式,输出运算结果

    36 求解简单的四则运算表达式 输入一个形式如 操作数 运算符 操作数 的四则运算表达式 输出运算结果 include
  • SpringCloud(注册中心)

    分布式架构与微服 restfu分格 入参的分格 rest分格 请求的分格 微服务 单体架构的应用场景 微服务的应用场景 上百个服务 服务于服务之间是有依赖关系的 什么是springcloud 当下流行的微服务 注册中心Eureka 注册中心
  • LeetCode-336.回文对、字典树、字符串翻转

    给定一组唯一的单词 找出所有不同 的索引对 i j 使得列表中的两个单词 words i words j 可拼接成回文串 示例 1 输入 abcd dcba lls s sssll 输出 0 1 1 0 3 2 2 4 解释 可拼接成的回文
  • N进制转十进制-C语言

    N进制到十进制 include
  • linux下eclipse集成tomcat(tomcatforEclipse)开发

    TomcatforEclipse http www eclipsetotale com 用linux 中的uzip 解压 zip 解压缩后 把解压后的文件夹放到 eclipse中的plugins中 插件特点 1 启动和停止Tomcat 4
  • IEnumerable和IEnumerator 详解

    初学C 的时候 老是被IEnumerable IEnumerator ICollection等这样的接口弄的糊里糊涂 我觉得有必要切底的弄清楚IEnumerable和IEnumerator的本质 下面我们先看IEnumerable和IEnu