天天看點

C#基礎知識系列九(對IEnumerable和IEnumerator接口的糊塗認識)

前言

   IEnumerable、IEnumerator到現在為止對這兩個接口還是不太了解,不了解但是自己總是想着試着要搞明白,畢竟自己用的少,是以在此先記錄一下。以備自己日後可以來翻查,同時也希望園子裡的大牛們,來幫我看看了解的怎麼樣。

檢視并使用兩個接口

  接下來我們先來看看兩個接口的定義。

  先來看一下IEnumerable接口,其實看過這個接口之後,發現它其實是非常的簡單,隻包含一個方法GetEnumerator(),它傳回一個可用于循環通路集合的IEnumerator對象,如下面截圖所示:

這裡的IEnumerator對象,其實就是另外一個接口,這個接口對象有什麼呢?它是一個真正的集合通路器,沒有它,就不能使用foreach語句周遊集合或數組,因為隻有IEnumerator對象才能通路集合中的項,假如連集合中的項都通路不了,那麼進行集合的循環周遊是不可能的事情了。那麼讓我們看看IEnumerator接口又定義了什麼東西。

C#基礎知識系列九(對IEnumerable和IEnumerator接口的糊塗認識)

從上面我們知道IEnumerator接口定義了一個Current屬性,MoveNext和Reset兩個方法,這是多麼的簡約。既然IEnumerator對象是一個通路器。那至少應該有一個Current屬性,來擷取目前集合中的項吧。MoveNext方法隻是将遊标的内部位置向前移動(就是移到一下個元素而已),要想進行循環周遊,不向前移動一下怎麼行呢?

通過注釋也可以明确的發現他們的用處。

下面我們來看一個簡單的例子:

static void Main(string[] args)
        {
            int[] iArr = { 1, 3, 4, 6, 7, 9 };
            foreach (int i in iArr)
            {
                Console.WriteLine(i.ToString());
            }
            Console.ReadLine();
        }      

F5來運作代碼

C#基礎知識系列九(對IEnumerable和IEnumerator接口的糊塗認識)

結果有了,說明簡單的數組是可以支援foreach循環的。

下面我們來自己來做一個小例子,先來定義實體類

/// <summary>
    /// 個人的實體類
    /// </summary>
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    /// <summary>
    /// 一群人的實體類
    /// </summary>
    public class People
    {            
        Person[] personList = new Person[4];
        public People()
        {
            personList[0] = new Person() { Name = "aehyok", Age = 25 };
            personList[1] = new Person() { Name = "Kris", Age = 22 };
            personList[2] = new Person() { Name = "Leo", Age = 21 };
            personList[3] = new Person() { Name = "Niki", Age = 23 };
        }
    }      

如上面代碼所示,一個Person類是個人的實體類,然後People類是一群人的實體類,按照和上面數組類似的格式,下面我們進行調用

static void Main(string[] args)
        {
       ///直接對一群人執行個體對象進行foreach
            People people = new People();
            foreach (Person p in people)
            {
                Console.WriteLine("Name:{0}\tAge{1}",p.Name,p.Age);
            }
            Console.ReadLine();
        }      

還沒來得及編譯,錯誤就來了

C#基礎知識系列九(對IEnumerable和IEnumerator接口的糊塗認識)

是以我們根據上面的講解我們就讓People類實作IEnumerable接口吧。現在先來修改People實體類。

/// <summary>
    /// 個人的實體類
    /// </summary>
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    /// <summary>
    /// 一群人的實體類
    /// </summary>
    public class People:IEnumerable
    {            
        Person[] personList = new Person[4];
        public People()
        {
            personList[0] = new Person() { Name = "aehyok", Age = 25 };
            personList[1] = new Person() { Name = "Kris", Age = 22 };
            personList[2] = new Person() { Name = "Leo", Age = 21 };
            personList[3] = new Person() { Name = "Niki", Age = 23 };
        }

        public IEnumerator GetEnumerator()
        {
            return this.personList.GetEnumerator();
        }
    }      

繼承實作接口,完成該方法之後,就可以在調用時用foreach了。

注意:其實這裡完全不用繼承該接口。直接對GetEnumerator()方法進行實作,然後傳回IEnumerator即可。

這樣還可以有另外一種調用方式

static void Main(string[] args)
        {
            People people = new People();
            foreach (Person p in people)
            {
                Console.WriteLine("Name:{0}\tAge{1}",p.Name,p.Age);
            }
            Console.WriteLine("");
            ///直接擷取疊代器
            IEnumerator i = people.GetEnumerator();
            while (i.MoveNext())
            {
                Person person = (Person)i.Current;
                Console.WriteLine("Name:{0}\tAge{1}", person.Name, person.Age);
            }
            Console.ReadLine();
        }      

調用結果

C#基礎知識系列九(對IEnumerable和IEnumerator接口的糊塗認識)

自定義兩個接口并進行實作

  上面我們是通過繼承微軟類庫中的接口來實作的實體集合的foreach周遊。下面我們來示範一下完全通過自己建立接口來實作自己定義的執行個體集合的foreach周遊。首先我們來實作一個簡單的疊代器。

第一步:定義一個接口IMyEnumerator,之後所有疊代器都要進行實作

/// <summary>
    /// 要求所有的疊代器全部實作該接口
    /// </summary>
    interface IMyEnumerator
    {
        bool MoveNext();
        object Current{get;};
    }      

第二步:再定義一個接口IMyEnumerable,所有集合要實作該接口

/// <summary>
    /// 要求所有的集合實作該接口
    /// 這樣一來,用戶端就可以針對該接口編碼
    /// 而無須關注具體的實作
    /// </summary>
    interface IMyEnumerable
    {
        IMyEnumerator GetEnumerator();
        int Count{get;};
    }      

第三步:一個簡單的集合類MyList,實作IMyEnumerable。

class MyList:IMyEnumerable
    {
        int[] items = {0,1,2,3,4,5,6,7,8,9};
        IMyEnumerator myEnumerator;

        public int this[int i]
        {
            get { return items[i]; }
            set { this.items[i] = value; }
        }

        public int Count
        {
            get { return items.Length; }
        }

        public IMyEnumerator GetEnumerator()
        {
            if (myEnumerator == null)
            {
                myEnumerator = new MyEnumerator(this);
            }
            return myEnumerator;
        }

    }      

第四步:其實集合中也需要進行使用實作了第一步的疊代器,是以在此就是要實作這個疊代器

public class MyEnumerator:IMyEnumerator
    {
        int index = 0;
        MyList myList;
        public MyEnumerator(MyList myList)
        {
            this.myList = myList;
        }

        public bool MoveNext()
        {
            if (index + 1 > =myList.Count)
            {
                index = 1;
                return false;
            }
            else
            {
                index++;
                return true;
            }
        }

        public object Current
        {
            get { return myList[index]; }
        }
    }      

第五步:簡單調用進行調試

static void Main(string[] args)
        {
            ///使用接口IMyEnumerable代替MyList
            IMyEnumerable list = new MyList();
            ///得到疊代器,在循環中針對疊代器進行編碼,而不是集合MyList
            IMyEnumerator enumerator = list.GetEnumerator();
            for (int i = 0; i < list.Count; i++)
            {
                object current = enumerator.Current;
                Console.WriteLine(current.ToString());
                enumerator.MoveNext();
                
            }
            Console.WriteLine("");
            ///重新建立一個新的對象
            IMyEnumerable list1 = new MyList();
            IMyEnumerator enumerator1 = list1.GetEnumerator();
            while (enumerator1.MoveNext())    //因為此處閑下移了一位,是以從1開始
            {
                object current = enumerator1.Current;
                Console.WriteLine(current.ToString());
            }
            Console.ReadLine();
        }      
C#基礎知識系列九(對IEnumerable和IEnumerator接口的糊塗認識)

   其實我定義的兩個接口使用的是IMyEnumerable和IMyEnumerator,這裡你直接可以去掉My那麼就是微軟類庫裡面的接口了,我這裡隻是自定義罷了,然後我自己定義接口的方法屬性,沒有嚴格按照微軟的接口進行定義,但是差不多,隻需要進行簡單的修正就可以進行調用。這裡有一個版本的。

C#基礎知識系列九(對IEnumerable和IEnumerator接口的糊塗認識)

View Code

其實上面例子中的調用我們就可以使用foreach來調用了,那麼現在我們來用foreach來調用看看。

static void Main(string[] args)
        {
            MyList list=new MyList();
            foreach (object obj in list)
            {
                Console.WriteLine(obj.ToString());
            }
            Console.ReadLine();
        }      

總結

通過上面我實作的幾個簡單的例子可以發現,一個類型支援foreach周遊的條件可以是:

  1、第一個方案是:這個類實作IEnumerable接口

  2、第二個方案是:這個類有一個public的GetEnumerator的執行個體方法(不用繼承IEnumerable實作接口),并且傳回類型中有public 的bool MoveNext()執行個體方法和public的Current執行個體屬性。

實作了IEnmerable<T>接口的集合,是強類型的。它為子對象的疊代提供類型更加安全的方式。

自己實作了下,感覺還是懂了一些,雖然還沒有徹底的搞明白,但最起碼大概知道怎麼回事了。有空再來看看yield關鍵字的用法。  

繼續閱讀