IEnumerator和IEnumerable详解

IEnumerator和IEnumerable

从名字常来看,IEnumerator是枚举器的意思,IEnumerable是可枚举的意思。

了解了两个接口代表的含义后,接着看源码:

IEnumerator:

public interface IEnumerator
    {
        // Interfaces are not serializable
        // Advances the enumerator to the next element of the enumeration and
        // returns a boolean indicating whether an element is available. Upon
        // creation, an enumerator is conceptually positioned before the first
        // element of the enumeration, and the first call to MoveNext
        // brings the first element of the enumeration into view.
        //
        bool MoveNext();

        // Returns the current element of the enumeration. The returned value is
        // undefined before the first call to MoveNext and following a
        // call to MoveNext that returned false. Multiple calls to
        // GetCurrent with no intervening calls to MoveNext
        // will return the same object.
        //
        Object Current {
            get;
        }

        // Resets the enumerator to the beginning of the enumeration, starting over.
        // The preferred behavior for Reset is to return the exact same enumeration.
        // This means if you modify the underlying collection then call Reset, your
        // IEnumerator will be invalid, just as it would have been if you had called
        // MoveNext or Current.
        //
        void Reset();
    }

IEnumerable:

    public interface IEnumerable
    {
        // Interfaces are not serializable
        // Returns an IEnumerator for this enumerable Object.  The enumerator provides
        // a simple way to access all the contents of a collection.
        [Pure]
        [DispId(-4)]
        IEnumerator GetEnumerator();
    }

发现IEnumerable只有一个GetEnumerator函数,返回值是IEnumerator类型,从注释我们可以得知IEnumerable代表继承此接口的类可以获取一个IEnumerator来实现枚举这个类中包含的集合中的元素的功能(比如List<T>,ArrayList,Dictionary等继承了IEnumeratble接口的类)。

用foreach来了解IEnumerable,IEnumerator的工作原理

我们模仿ArrayList来实现一个简单的ConstArrayList,然后用foreach遍历。

//一个常量的数组,用于foreach遍历
class ConstArrayList : IEnumerable
{
    public int[] constItems = new int[] { 1, 2, 3, 4, 5 };
    public IEnumerator GetEnumerator()
    {
        return new ConstArrayListEnumeratorSimple(this);
    }
}
//这个常量数组的迭代器
class ConstArrayListEnumeratorSimple : IEnumerator
{
    ConstArrayList list;
    int index;
    int currentElement;
    public ConstArrayListEnumeratorSimple(ConstArrayList _list)
    {
        list = _list;
        index = -1;
    }

    public object Current
    {
        get
        {
            return currentElement;
        }
    }

    public bool MoveNext()
    {
        if(index < list.constItems.Length - 1)
        {
            currentElement = list.constItems[++index];
            return true;
        }
        else
        {
            currentElement = -1;
            return false;
        }
    }

    public void Reset()
    {
        index = -1;
    }
}
class Program
{
    static void Main(string[] args)
    {
        ConstArrayList constArrayList = new ConstArrayList();
        foreach(int item in constArrayList)
        {
            WriteLine(item);
        }
        ReadKey();
    }
}

输出结果:

1

2

3

4

5

代码达到了遍历效果,但是在用foreach遍历时,IEnumerator和IEnumerable究竟是如何运行的,我们可以通过增加增加日志可以直观的看到原因。

//一个常量的数组,用于foreach遍历
class ConstArrayList : IEnumerable
{
    public int[] constItems = new int[] { 1, 2, 3, 4, 5 };
    public IEnumerator GetEnumerator()
    {
        WriteLine("GetIEnumerator");
        return new ConstArrayListEnumeratorSimple(this);
    }
}
//这个常量数组的迭代器
class ConstArrayListEnumeratorSimple : IEnumerator
{
    ConstArrayList list;
    int index;
    int currentElement;
    public ConstArrayListEnumeratorSimple(ConstArrayList _list)
    {
        list = _list;
        index = -1;
    }

    public object Current
    {
        get
        {
            WriteLine("Current");
            return currentElement;
        }
    }

    public bool MoveNext()
    {
        if(index < list.constItems.Length - 1)
        {
            WriteLine("MoveNext true");
            currentElement = list.constItems[++index];
            return true;
        }
        else
        {
            WriteLine("MoveNext false");
            currentElement = -1;
            return false;
        }
    }

    public void Reset()
    {
        WriteLine("Reset");
        index = -1;
    }
}
class Program
{
    static void Main(string[] args)
    {
        ConstArrayList constArrayList = new ConstArrayList();
        foreach(int item in constArrayList)
        {
            WriteLine(item);
        }
        ReadKey();
    }
}

输出结果:

GetIEnumerator

MoveNext true

Current

1

MoveNext true

Current

2

MoveNext true

Current

3

MoveNext true

Current

4

MoveNext true

Current

5

MoveNext false

通过输出结果,我们可以发现,foreach在运行时会先调用ConstArrayList的GetIEnumerator函数获取一个ConstArrayListEnumeratorSimple,之后通过循环调用ConstArrayListEnumeratorSimple的MoveNext函数,index后移,更新Current属性,然后返回Current属性,直到MoveNext返回false。

总结一下:

GetIEnumerator()负责获取枚举器。

MoveNext()负责让Current获取下一个值,并判断遍历是否结束。

Current负责返回当前指向的值。

Rest()负责重置枚举器的状态(在foreach中没有用到)

这些就是IEnumerable,IEnumerator的基本工作原理了。

其次我们发现:

ConstArrayList constArrayList = new ConstArrayList();
foreach(int item in constArrayList)
{
    writeLine(item);
}

其实就等价于:

ConstArrayList constArrayList = new ConstArrayList();
IEnumerator enumeratorSimple = constArrayList.GetEnumerator();
while (enumeratorSimple.MoveNext())
{
    int item = (int)enumeratorSimple.Current;
    WriteLine(item);
}

也就是说foreach其实是一种语法糖,用来简化对可枚举元素的遍历代码。而被遍历的类通过实现IEnumerable接口和一个相关的IEnumerator枚举器来实现遍历功能。

原文地址:https://www.cnblogs.com/blueberryzzz/p/8667261.html

时间: 2024-08-04 09:17:55

IEnumerator和IEnumerable详解的相关文章

IEnumerator/ IEnumerable/ yield return/ StartCoroutine 详解

IEnumerator/ IEnumerable public interface IEnumerable { IEnumerator GetEnumerator(); } public interface IEnumerator { bool MoveNext(); void Reset(); Object Current { get; } } 在两者的使用上,有下面几点需要注意 1.一个Collection要支持foreach方式的遍历,必须实现IEnumerable接口(亦即,必须以某种方

IEnumerable&lt;T&gt; 接口和GetEnumerator 详解

IEnumerable<T> 接口 .NET Framework 4.6 and 4.5 公开枚举数,该枚举数支持在指定类型的集合上进行简单迭代. 若要浏览此类型的.NET Framework 源代码,请参阅参考源. 命名空间:  System.Collections.Generic程序集:  mscorlib(在 mscorlib.dll 中) public interface IEnumerable<out T> : IEnumerable 类型参数    out T 要枚举的

Linq之旅:Linq入门详解(Linq to Objects)

示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集成查询).通过LINQ,我们可以使用相同API操作不同的数据源.接下来就让我们看看LINQ是什么以及如何使用? 再此之前,需要先了解的相关技术 1. 隐式类型.匿名类型.对象初始化器 1) 隐式类型,使用var关键字创建,C#编译器会根据用于初始化局部变量的初始值推断出变量的数据类型.(不过我个人认

C# LINQ详解(转)

C# LINQ详解(一) 原文标题:How does it work in C#?-Part 3 (C# LINQ in detail),作者:Mohammand A Rahman. 目录 LINQ 基础 扩展方法-幕后的工作 扩展方法列表 Where and Select All Average Concat Contains Count DefaultIfEmpty Distinct ElementAt Empty Except First FirstOrDefault Union Inte

详解C# 迭代器

迭代器模式是设计模式中行为模式(behavioral pattern)的一个例子,他是一种简化对象间通讯的模式,也是一种非常容易理解和使用的模式.简单来说,迭代器模式使得你能够获取到序列中的所有元素而不用关心是其类型是array,list,linked list或者是其他什么序列结构.这一点使得能够非常高效的构建数据处理通道(data pipeline)--即数据能够进入处理通道,进行一系列的变换,或者过滤,然后得到结果.事实上,这正是LINQ的核心模式. 在.NET中,迭代器模式被IEnume

Delphi TStringHelper用法详解

Delphi TStringHelper用法详解 (2013-08-27 22:45:42) 转载▼ 标签: delphi_xe5 it 分类: Delphi Delphi XE4的TStringHelper,对操作字符串进一步带来更多的方法,使用这些方法才可以实现跨平台的代码. System.SysUtils.TStringHelper 大小写转换:-------------------------------------------------------------------------

EF并发详解

EntityFramework Core高并发深挖详解,一纸长文,你准备好看完了吗? 前言 之前有关EF并发探讨过几次,但是呢,博主感觉还是有问题,为什么会觉得有问题,其实就是理解不够透彻罢了,于是在项目中都是用的存储过程或者SQL语句来实现,利用放假时间好好补补EF Core并发的问题,本文比较长,请耐心点看. EntityFramework Core并发初级版初探 关于并发无非就两种:乐观并发和悲观并发,悲观并发简言之则是当客户端对数据库中同一值进行修改时会造成阻塞,而乐观并发则任何客户端都

日志模型详解

日志模型详解 NET Core的日志模型主要由三个核心对象构成,它们分别是Logger.LoggerProvider和LoggerFactory.总的来说,LoggerProvider提供一个具体的Logger对象将格式化的日志消息写入相应的目的地,但是我们在编程过程中使用的Logger对象则由LoggerFactory创建,这个Logger利用注册到LoggerFactory的LoggerProvider来提供真正具有日志写入功能的Logger,并委托后者来记录日志. 目录一.Logger  

C# 集合详解 (适合新手)

System.Collections 命名空间包含接口和类,这些接口和类定义各种对象(如列表.队列.位数组.哈希表和字典)的集合.System.Collections.Generic 命名空间包含定义泛型集合的接口和类,泛型集合允许用户创建强类型集合,它能提供比非泛型强类型集合更好的类型安全性和性能.System.Collections.Specialized 命名空间包含专用的和强类型的集合,例如,链接的列表词典.位向量以及只包含字符串的集合. 在System.Collections命名空间中