IEnumerable


什么是IEnumerable?

IEnumerable及IEnumerable的泛型版本IEnumerable<T>是一個接口,它只含有一個方法GetEnumerator。Enumerable這個靜態類型含有很多擴展方法,其擴展的目標是IEnumerable<T>。

實現了這個接口的類可以使用Foreach關鍵字進行迭代(迭代的意思是對於一個集合,可以逐一取出元素並遍歷之)。實現這個接口必須實現方法GetEnumerator。

實現一個繼承IEnumerable的類型等同於實現方法GetEnumerator。想知道如何實現方法GetEnumerator,不妨思考下實現了GetEnumerator之后的類型在Foreach之下的行為:

  • 可以獲得第一個或當前成員
  • 可以移動到下一個成員
  • 可以在集合沒有下一個成員時退出循環。

假設我們有一個很簡單的Person類(例子來自MSDN):

    public class Person
    {
        public Person(string fName, string lName)
        {
            FirstName = fName;
            LastName = lName;
        }

        public string FirstName;
        public string LastName;
    }

然后我們想構造一個沒有實現IEnumerable的類型,其儲存多個Person,然后再對這個類型實現IEnumerable。這個類型實際上的作用就相當於Person[]或List<Person>,但我們不能使用它們,因為它們已經實現了IEnumerable,故我們構造一個People類,模擬很多人(People是Person的復數形式)。這個類型允許我們傳入一組Person的數組。所以它應當有一個Person[]類型的成員,和一個構造函數,其可以接受一個Person[],然后將Person[]類型的成員填充進去作為初始化。

//People類就是Person類的集合
    //但我們不能用List<Person>或者Person[],因為他們都實現了IEnumerable
    //我們要自己實現一個IEnumerable
    //所以請將People類想象成List<Person>或者類似物
    public class People : IEnumerable
    {
        private readonly Person[] _people;
        public People(Person[] pArray)
        {
            //構造一個Person的集合
            _people = new Person[pArray.Length];

            for (var i = 0; i < pArray.Length; i++)
            {
                _people[i] = pArray[i];
            }
        }

        //實現IEnumerable需要實現GetEnumerator方法
        public IEnumerator GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

我們的主函數應當是:

 public static void Main(string[] args)
        {
            //新的Person數組
            Person[] peopleArray = 
            {
                new Person("John", "Smith"),
                new Person("Jim", "Johnson"),
                new Person("Sue", "Rabon"),
            };

            //People類實現了IEnumerable
            var peopleList = new People(peopleArray);

            //枚舉時先訪問MoveNext方法
            //如果返回真,則獲得當前對象,返回假,就退出此次枚舉
            foreach (Person p in peopleList)
                Console.WriteLine(p.FirstName + " " + p.LastName);
        }
復制代碼

但現在我們的程序不能運行,因為我們還沒實現GetEnumerator方法。

實現方法GetEnumerator

GetEnumerator方法需要一個IEnumerator類型的返回值,這個類型是一個接口,所以我們不能這樣寫:

return new IEnumerator();

因為我們不能實例化一個接口。我們必須再寫一個類PeopleEnumerator,它繼承這個接口,實現這個接口所有的成員:Current屬性,兩個方法MoveNext和Reset。於是我們的代碼又變成了這樣:

        //實現IEnumerable需要實現GetEnumerator方法
        public IEnumerator GetEnumerator()
        {
            return new PeopleEnumerator();
        }

在類型中:

    public class PeopleEnumerator : IEnumerator
    {
        public bool MoveNext()
        {
            throw new NotImplementedException();
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }

        public object Current { get; }
    }

現在問題轉移為實現兩個方法,它們的功能看上去一目了然:一個負責將集合中Current向后移動一位,一個則將Current初始化為0。我們可以查看IEnumerator元數據,其解釋十分清楚:

  • Enumerator代表一個類似箭頭的東西,它指向這個集合當前迭代指向的成員
  • IEnumerator接口類型對非泛型集合實現迭代
  • Current表示集合當前的元素,我們需要用它僅有的get方法取得當前元素
  • MoveNext方法根據Enumerator是否可以繼續向后移動返回真或假
  • Reset方法將Enumerator移到集合的開頭

通過上面的文字,我們可以理解GetEnumerator方法,就是獲得當前Enumerator指向的成員。我們引入一個整型變量position來記錄當前的位置,並且先試着寫下:

public class PeopleEnumerator : IEnumerator
    {
        public Person[] _peoples;
        public object Current { get; }

        //當前位置
        public int position;

        //構造函數接受外部一個集合並初始化自己內部的屬性_peoples
        public PeopleEnumerator(Person[] peoples)
        {
            _peoples = peoples;           
        }
        
        //如果沒到集合的尾部就移動position,返回一個bool
        public bool MoveNext()
        {
            if (position < _peoples.Length)
            {
                position++;
                return true;
            }
            return false;
        }

        public void Reset()
        {
            position = 0;
        }
    }

 

 

這看上去好像沒問題,但一執行之后卻發現:

  • 當執行到MoveNext方法時,position會先增加1,這導致第一個元素(在位置0)會被遺漏,故position的初始值應當為-1而不是0
  • 當前位置變量position顯然應該是私有的
  • 需要編寫Current屬性的get方法取出當前位置(position)上的集合成員

通過不斷的調試,最后完整的實現應當是:

public class PeopleEnumerator : IEnumerator
{
        public Person[] People;

        //每次運行到MoveNext或Reset時,利用get方法自動更新當前位置指向的對象
        object IEnumerator.Current
        {
            get
            {
                try
                {
                    //當前位置的對象
                    return People[_position];
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }

        //當前位置
        private int _position = -1;

        public PeopleEnumerator(Person[] people)
        {
            People = people;           
        }

        //當程序運行到foreach循環中的in時,就調用這個方法獲得下一個person對象
        public bool MoveNext()
        {
            _position++;
            //返回一個布爾值,如果為真,則說明枚舉沒有結束。
            //如果為假,說明已經到集合的結尾,就結束此次枚舉
            return (_position < People.Length);
        }

        public void Reset() => _position = -1;
    }

為什么當程序運行到in時,會呼叫方法MoveNext呢?我們並沒有直接調用這個方法啊?當你試圖查詢IL時,就會得到答案。實際上下面兩段代碼的作用是相同的:

foreach (T item in collection)
{
  ...
}
IEnumerator<T> enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
  T item = enumerator.Current;
  ...
}

 

轉載:https://www.cnblogs.com/haoyifei/p/5768379.html
 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM