欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

IEnumerator和IEnumerable详解

程序员文章站 2022-06-23 16:09:49
IEnumerator和IEnumerable 从名字常来看,IEnumerator是枚举器的意思,IEnumerable是可枚举的意思。 了解了两个接口代表的含义后,接着看源码: IEnumerator: IEnumerable: 发现IEnumerable只有一个GetEnumerator函数, ......

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枚举器来实现遍历功能。