C # IEnumerable, ienumerator interface

Time:2022-5-11

A long time ago, when learning WinForm, I started to release c#, and then put it off for a long time. I picked up C again when I was studying unity recently #. I found that I had forgotten almost everything I had learned before. It is often used especially in unityyieldKeywords andIEnumeratorInterface to do some delay loop operations. As the first step to regain C # first, let’s review and summarize the in C #IEnumerableandIEnumeratorInterface.

background

In many programming scenarios, we need to iterate over a collection. In order to simplify this process, many high-level languages use traversal statements to operate, such asfor...in...perhapsforeach()。 For example, in C #, the instance of ArrayList can be traversed in foreach (). Of course, except for some native collection classes of the language, not all class instances can be operated in traversal statements. If you want the instance of a class to be traversed in the traversal statement, you must make the class implement some interfaces or properties according to the provisions of the language.

Native ArrayList

Before we get down to business, let’s take a look at how c# native ArrayList works.

using System.IO;
using System;
using System.Collections;

class Test
{
    static void Main()
    {
        ArrayList array = new ArrayList();
        array.Add(1);
        array.Add(2);
        array.Add("3");
        array.Add(4);
        foreach(object i in array){
            Console.WriteLine(i);
        }
    }
}

It’s simple, isn’t it? Let’s start with itIEnumerableandIEnumeratorInterface. And implement its own versionArrayList

Start implementing your own ArrayList

First of all, let’s meet our protagonist today,IEnumerable, IEnumerable<T>, IEnumerator, IEnumberator<T>, they really look alike. First of all, we can see at a glance that there are types behind them (c# is calledgeneric)。 The generic version is slightly different from the non generic version. As a start, let’s implement the non generic version first. Then choose our male and female,IEnumerable, IEnumerator。 Take a look at their resumes. The official document reads:

IEnumerable is the base interface for all non-generic collections that can be enumerated. For the generic version of this interface see System.Collections.Generic.IEnumerable. IEnumerable contains a single method, GetEnumerator, which returns an IEnumerator. IEnumerator provides the ability to iterate through the collection by exposing a Current property and MoveNext and Reset methods.

IEnumerableIt is the basic interface to be implemented in those collections that can be traversed,IEnumerableThere is one wayGetEnumerator(), this method sends back aIEnumeratorType,IEnumeratorInclude oneCurrentProperties andMoveNextandResetMethod, through these properties and methods, you can traverse the collection. So our own ArrayList should be implemented as follows:

public class MyArrayList: IEnumerable
{

    //some code
    
    public IEnumerator GetEnumerator()
    {
       //some code
       //return new MyEnumerator(...);
    }
}

It can be seen that GetEnumerator returns an ienumerator type, so we must implement our own ienumerator class:

public class MyEnumerator:IEnumerator
{
    
    public bool MoveNext()
    {
        //some code
    }
    
    public void Reset()
    {
        //some code
    }
    
    public object Current
    {
        get
        {
            // some code
        }
    }
}

After knowing the basic structure, we can expand the ArrayList structure we want. The complete code is as follows:

using System.IO;
using System;
using System.Collections;


public class MyArrayList
{
    
    object[] data;
    int currentIndex;
    
    public MyArrayList(int length)
    {
        this.data = new object[length];
        currentIndex = 0;
    }
    
    public void Add(object s)
    {
        data[currentIndex++] = s;
    }
    
    public IEnumerator GetEnumerator()
    {
        return new MyEnumerator(data);
    }
}

public class MyEnumerator:IEnumerator
{
    
    private object[] _data;
    private int position = -1;
    
    public MyEnumerator(object[] data)
    {
        _data = data;
    }
    
    public bool MoveNext()
    {
        position++;
        return (position < _data.Length);
    }
    
    public void Reset()
    {
        position = -1;
    }
    
    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }
    
    public object Current
    {
        get
        {
            return _data[position];
        }
    }
}

class Test
{
    static void Main()
    {
        MyArrayList array = new MyArrayList(10);
        array.Add("Jack");
        array.Add("Tom");
        foreach(object i in array)
        {
            Console.WriteLine(i);
        }
    }
}

Such a simple ArrayList is implemented. Another thing to note isIEnumerableThe interface does not have to be implemented, but it must be implemented in order to traverseGetEnumerator()method. But realizeIEnumerableandIEnumberatorInterface is a good habit.

Generic versions of IEnumerable and ienumerator

aboutIEnumerable<T>andIEnumerator<T>The implementation of is slightly different if we just put the code in the above codeIEnumerableandIEnumeratorReplace with the corresponding generic interface:

public class MyArrayList<T>: IEnumerable<T>
{

    //some code
    
    public IEnumerator<T> GetEnumerator()
    {
       //some code
       //return new MyEnumerator<T>(...);
    }
}
public class MyEnumerator<T>:IEnumerator<T>
{
    
    public bool MoveNext()
    {
        //some code
    }
    
    public void Reset()
    {
        //some code
    }
    
    public T Current
    {
        get
        {
            // some code
        }
    }
}

The compiler will report three errors:

1.MyEnumerator<T> does not implement interface member System.IDisposable.Dispose()
2.MyArrayList<T> does not implement interface member System.Collections.IEnumerable.GetEnumerator() and the best implementing candidate MyArrayList<T>.GetEnumerator() return type System.Collections.Generic.IEnumerator<T> does not match interface member return type System.Collections.IEnumerator
3.MyEnumerator<T> does not implement interface member System.Collections.IEnumerator.Current.get and the best implement ing candidate MyEnumerator<T>.Current.get return type T does not match interface member return type object

The first mistake tells usIEnumerable<T>To achieveDispose()Method, the second and third errors need us to implementIEnumerable.GetEnumerator()andIEnumerator.CurrentProperties. The complete code is as follows:

using System.IO;
using System;
using System.Collections;
using System.Collections.Generic;


public class MyArrayList<T>: IEnumerable<T>
{
    
    T[] data;
    int currentIndex;
    
    public MyArrayList(int length)
    {
        this.data = new T[length];
        currentIndex = 0;
    }
    
    public void Add(T s)
    {
        data[currentIndex++] = s;
    }
    
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    
    public IEnumerator<T> GetEnumerator()
    {
        return new MyEnumerator<T>(data);
    }
}

public class MyEnumerator<T>:IEnumerator<T>
{
    
    private T[] _data;
    private int position = -1;
    
    public MyEnumerator(T[] data)
    {
        _data = data;
    }
    
    public bool MoveNext()
    {
        position++;
        return (position < _data.Length);
    }
    
    public void Reset()
    {
        position = -1;
    }
    
    public void Dispose()
    {
        //Dispose the resource
    }
    
    object IEnumerator.Current
    {
        get
        {
            return Current;    
        }
    }
    
    public T Current
    {
        get
        {
            return _data[position];
        }
    }
}

public class Test
{
    static void Main()
    {
        MyArrayList<string> array = new MyArrayList<string>(10);
        array.Add("Jack");
        array.Add("Tom");
        foreach(string str in array)
        {
            Console.WriteLine(str);
        }
    }
}

Yield keyword

How many of the above methods can be implemented by traversing one or more classesIEnumeratorClass. Moreover, the generic version needs to implement several more methods and attributes.yieldKeywords can help us simplify the above process.yieldUsed to generate a traversal type, includingyieldThe return value type of the method must beIEnumerable,IEnumerator,IEnumerable<T>,IEnumerator<T>One of them.yieldThe general form of isyield return <expression>。 For example:

public class PowersOf2
{
    static void Main()
    {
        // Display powers of 2 up to the exponent of 8:
        foreach (int i in Power(2, 8))
        {
            Console.Write("{0} ", i);
        }
    }

    public static System.Collections.Generic.IEnumerable<int> Power(int number, int exponent)
    {
        int result = 1;

        for (int i = 0; i < exponent; i++)
        {
            result = result * number;
            yield return result;
        }
    }

    // Output: 2 4 8 16 32 64 128 256
}

Call the abovePowerMethod does not execute the body of the function, but returns aIEnumerable<int>Object, inforeachIn, callMoveNextTo traverse. At this time, the function starts to execute, and the guidance is metyield, the object returned later is the value of the current property. The next time MoveNext is called, it will continue to execute from the previous yield. In this way, when the function ends, the traversal ends. After knowing this feature, we can simplify the above code:

using System.IO;
using System;
using System.Collections;
using System.Collections.Generic;


public class MyArrayList<T>: IEnumerable<T>
{
    
    T[] data;
    int currentIndex;
    
    public MyArrayList(int length)
    {
        this.data = new T[length];
        currentIndex = 0;
    }
    
    public void Add(T s)
    {
        data[currentIndex++] = s;
    }
    
    IEnumerator IEnumerable.GetEnumerator(){
        return this.GetEnumerator();
    }
    
    public IEnumerator<T> GetEnumerator()
    {
        for(int i = 0; i < data.Length; i++)
        {
            yield return data[i];
        }
    }
}

public class Test
{
    static void Main()
    {
        MyArrayList<string> array = new MyArrayList<string>(10);
        array.Add("Jack");
        array.Add("Tom");
        foreach(string str in array)
        {
            Console.WriteLine(str);
        }
    }
}

summary

For the implementation of traversal, each language has its own different implementation, but it has great similarities. Understanding the implementation of one language is also helpful to understand the implementation of other languages. After that, I will write the implementation of traversal in JavaScript.

Recommended Today

PHP development version problem handling

When installing an open source project, the author used PHP 7.1 locally for development, while my local is PHP 7.0, so a tragedy occurred. Such an error occurred:requires php ^7.1 -> your PHP version (7.0.13) does not satisfy that requirement.. 1、 Composer installation package Error after composer install [email protected]:~/Code/sample$ composer install Loading composer repositories with […]