C# ~ 從 IEnumerable / IEnumerator 到 IEnumerable / IEnumerator 到 yield

IEnumerable / IEnumerator

首先,IEnumerable / IEnumerator 接口定義如下:

public interface IEnumerable  /// 可枚舉接口
{
    IEnumerator GetEnumerator();
} 
public interface IEnumerator  /// 枚舉器接口
{
    object Current { get; }  
    bool MoveNext();
    void Reset();
}

:Current 沒有 set 方法,在 foreach 中不能修改元素 var item 的值。
· IEnumerable:聲明式的接口,聲明實現了該接口的類是可枚舉類型;
· IEnumerator:實現式的接口,IEnumerator 對象說明如何實現一個枚舉器;

通過繼承 IEnumerable / IEnumerator 接口實現自定義類使用 foreach 語句來遍歷自身元素。邏輯關係圖:
   精減的團隊模型 - R模型

   People <-> MyClass 實現 IEnumerable 接口的 GetEnumerator()方法
   EnumeratorPeople <-> MyEnumerator 實現 IEnumerator 接口

·  定義Person類

 public class Person
 {
   private string name;
   private int age;

   public Person(string _name, int _age){
      this.name = _name, this.age = _age;
   }
   public override string ToString(){
      return string.Format("Name:{0},Age:{1}.", name, age);
   }
 }

  數組定義見主函數,以下2種遍歷數組的方法等同,因爲所有數組的基類都是 System.Array ,System.Array 類實現了 IEnumerable 接口,可以直接通過 GetEnumerator() 方法返回枚舉數對象,枚舉數對象可以依次返回請求的數組的元素。

   // 利用 foreach 遍歷數組
   foreach (var person in persons)
        Console.WriteLine(person.ToString());
   // 利用 IEnumerable ~ IEnumerator 遍歷數組    
   IEnumerator it = persons.GetEnumerator();
   while (it.MoveNext()){
        Person obj = (Person)(it.Current);  // 需強制類型轉換
        Console.WriteLine(obj.ToString());
   }

·  定義People類 (MyClass)

  public class People 
  {
        private Person[] persons;
        public People(Person[] _persons){ 
            persons = new Person[_persons.Length];
            for (int i = 0; i < _persons.Length; ++i){
                 persons[i] = _persons[i];    
            }
        }
  }

 注意,People 類的 persons 數組是 private 變量,在主測函數中是無法遍歷訪問的。
 方法 1:將 private 更改爲 public:

    foreach (var person in people.persons)
        Console.WriteLine(person.ToString());

 方法 2:People 類繼承 IEnumerable 接口並實現 GetEnumerator() 方法,有 2 種方法:
  [-1-]. 利用數組默認實現了 IEnumerable 和 IEnumerator 接口的事實,重新定義 People 類:

  public class People : IEnumerable
  {
        ... ...
        public IEnumerator GetEnumerator(){
            return persons.GetEnumerator();  // 方法 1
        }
  }

  [-2-]. 自定義枚舉數類 (EnumeratorPeople 類如下),繼承並實現 IEnumerator 接口,重新定義 People 類:

  public class People : IEnumerable
  {
        ... ...
        public IEnumerator GetEnumerator(){
            return new EnumeratorPeople(persons);  // 方法 2 
        }
  }

 此時,在方法2中自定義類可以使用如下 foreach 語句來遍歷自身元素:

    foreach (var person in people)
        Console.WriteLine(person.ToString());

·  定義EnumeratorPeople類 (MyEnumerator)

  public class EnumeratorPeople : IEnumerator
  {
        private int position = -1;
        private Person[] persons;
        public EnumeratorPeople(Person[] _persons){ 
            persons = new Person[_persons.Length];
            for (int i = 0; i < _persons.Length; ++i)
                persons[i] = _persons[i];
        }

        public object Current{
            get{ return persons[position]; }
        }
        public bool MoveNext(){
            ++position;
            return (position < persons.Length); 
        }
        public void Reset(){
            position = -1;
        }
  }

·主函數測試代碼

    class Program{
        static void Main(string[] args){
            Person[] persons = { 
                new Person("abc",25),  new Person("xyz",22),
                new Person("qwer",12), new Person("pm",20)  };
            People people = new People(persons);
        }
    }
總結

 一個類型是否支持foreach遍歷,本質上是實現 IEnumerator 接口,2 種方法:
(1)自定義類只要繼承 IEnumerable 接口並實現無參 GetEnumerator() 方法即可,最簡單;
(2)在(1)基礎上,定義 MyEnumerator 類繼承並實現 IEnumerator 接口;

擴展

 foreach 語句隱式調用集合的無參 GetEnumerator() 方法。其實,不論集合是否有實現 IEnumerable 接口,只要必須提供無參 GetEnumerator() 方法並返回包含 Current 屬性和 MoveNext() 方法的 IEnumerator 對象即可,然後編譯器會自動去綁定,無需依賴 IEnumerable 和 IEnumerator 接口,從而實現 foreach 對自定義集合類的遍歷。

  public class People
  {
    public class EnumeratorPeople
    { 
            private int position = -1;
            private Person[] enumPersons; 
            public EnumeratorPeople(Person[] _persons){
                enumPersons = new Person[_persons.Length];
                for (int i = 0; i < _persons.Length; ++i){
                    enumPersons[i] = _persons[i];
                }
            }

            public object Current{
                get { return enumPersons[position]; }
            }
            public bool MoveNext(){
                ++position;
                return ( position < enumPersons.Length );
            }
            public void Reset(){
                position = -1;
            }
       }

        private Person[] persons;
        public People(Person[] _persons){
            persons = new Person[_persons.Length];
            for (int i = 0; i < _persons.Length; ++i){
                persons[i] = _persons[i];
            }
        }
        public EnumeratorPeople GetEnumerator(){
            return new EnumeratorPeople(persons);
        }
  }

此處,枚舉數類聲明爲嵌套類,或者集成爲一個類,也可以分成單獨的 2 個類均可。

延伸問題
·  for 與 foreach
 for 先取全部再遍歷,foreach 邊遍歷邊取值;
·  Linq to Object 中返回 IEnumerable 類型?
 IEnumerable 是延遲加載的。


參考

·傳統遍歷與迭代器
·IEnumerable和IEnumerator 詳解
·自定義類實現foreach深入理解 foreach


IEnumerable<T> / IEnumerator<T>

優缺點對比
·  非泛型:非類型安全,返回object類型的引用、需要再轉化爲實際類型 (值類型需要裝箱和拆箱);
·  泛型:類型安全,直接返回實際類型的引用;
首先,IEnumerable<T> / IEnumerator<T> 接口定義如下:

public interface IEnumerable<out T> : IEnumerable  
{
    IEnumerator<T> GetEnumerator();
} 
public interface IEnumerator<out T> : IEnumerator, IDisposable  
{
    T Current { get; }
}

其中,接口 IDisposable 定義爲:

public interface IDisposable{
    void Dispose();
}

邏輯關係圖:
   精減的團隊模型 - R模型 

   People <-> MyClass 實現 IEnumerable<T> 接口的 泛型GetEnumerator()方法
   GenEnumPeople <-> MyGenEnumerator 實現 IEnumerator<T> 接口

顯式實現非泛型版本,在類中實現泛型版本!如下,類 MyGenEnumerator 實現了 IEnumerator<T>,類MyClass 實現了 IEnumerable<T> 接口。

public class MyClass : IEnumerable<T>
{
    public IEnumerator<T> GetEnumerator() { }     // IEnumerable<T> 版本
    IEnumerator IEnumerable.GetEnumerator() { }   // IEnumerable 版本
}

public class MyGenEnumerator : IEnumerator<T>
{
    public T Current { get; }            // IEnumerator<T> 版本
    public bool MoveNext() { }
    public void Reset() { }
    object IEnumerator.Current { get; }  // IEnumerator 版本
    public void Dispose() { }
}

·  定義 People 類 (MyClass)  

   public class People : IEnumerable<Person>
   {
        private Person[] persons;
        public People(Person[] _persons){
            persons = new Person[_persons.Length];
            for (int i = 0; i < _persons.Length; ++i)
                persons[i] = _persons[i];
        }

        public IEnumerator<Person> GetEnumerator(){
            return new GenericEnumeratorPeople(persons);
        }
        IEnumerator IEnumerable.GetEnumerator(){   // 顯式實現
            return this.GetEnumerator();
        }
   }

·  定義 GenEnumPeople 類 (MyGenEnumerator)

   public class GenEnumPeople : IEnumerator<Person>
   {
        private int position = -1;
        private Person[] persons;
        public GenericEnumeratorPeople(Person[] _persons){ 
            persons = new Person[_persons.Length];
            for (int i = 0; i < _persons.Length; ++i)
                persons[i] = _persons[i];
        }

        public Person Current{
            get { return persons[position]; }
        }
        object IEnumerator.Current{    // 顯式實現
            get { return this.Current; }
        }
        public bool MoveNext(){
            ++position;
            return (position < persons.Length);
        }
        public void Reset() { position = -1; }
        public void Dispose() { Console.WriteLine("void Dispose()"); }
   }

其中,IDisposable 接口的學習參見 由 IDisposable 到 GC

擴展 泛型委託應用

迭代器

  C#2.0 利用迭代器可以簡單實現 GetEnumerator() 函數。迭代器是用於返回相同類型的值的有序集合的一段代碼。利用 yield 關鍵字,實現控制權的傳遞和循環變量的暫存,使類或結構支持 foreach 迭代,而不必顯式實現 IEnumerable 或 IEnumerator 接口,由 JIT 編譯器輔助編譯成實現了 IEnumerable 或 IEnumerator 接口的對象。yield return 提供了迭代器一個重要功能,即取到一個數據後馬上返回該數據,不需要全部數據加載完畢,有效提高遍歷效率(延遲加載)。
 - yield 關鍵字用於指定返回的值,yield return 語句依次返回每個元素,yield break 語句終止迭代;
 - 到達 yield return 語句時,保存當前位置,下次調用迭代器時直接從當前位置繼續執行;
 - 迭代器可以用作方法、運算符或get訪問器的主體實現,yield 語句不能出現在匿名方法中;
 - 迭代器返回類型必須爲 IEnumerable、IEnumerator、IEnumerable<T> 或 IEnumerator<T>;
 
邏輯關係圖: 
   精減的團隊模型 - R模型 
   
· ·返回 IEnumerator 類型
 返回此類型的迭代器通常爲默認迭代器
 ·  People 類

 public class People
 {
   private Person[] persons;
   public People(Person[] _persons){
      persons = new Person[_persons.Length];
      for (int i = 0; i < _persons.Length; ++i)
         persons[i] = _persons[i];
   }

   public IEnumerator GetEnumerator(){
      return IterMethod1;      // [1]
      return IterMethod2();    // [2]
   }
   public IEnumerator IterMethod1     // [1].屬性返回 IEnumerator
   {
      get {
         for (int i = 0; i < persons.Length; ++i)
            yield return persons[i];
      }
   }
   public IEnumerator IterMethod2()   // [2].函數返回 IEnumerator (推薦)
   {
      for (int i = 0; i < persons.Length; ++i)
         yield return persons[i];
   }
 }

·主函數測試代碼

 People people = new People(persons);
 foreach (var person in people)
    Console.WriteLine(person.ToString());

· ·返回 IEnumerable 類型
 返回此類型的迭代器通常用於實現自定義迭代器
 ·  People 類

 public class People
 {
    private Person[] persons;
    public People(Person[] _persons){
      persons = new Person[_persons.Length];
      for (int i = 0; i < _persons.Length; ++i)
         persons[i] = _persons[i];
    }
   
    public IEnumerator GetEnumerator(){
      return IterMethod1.GetEnumerator();      // [1]
      return IterMethod2().GetEnumerator();    // [2]
    }
    public IEnumerable IterMethod1    // [1].自定義迭代器 1
    {
      get{
         for (int i = 0; i < persons.Length; ++i)
            yield return persons[i];
      }
    }
    public IEnumerable IterMethod2()  // [2].自定義迭代器 2 (推薦)
    {
      for (int i = 0; i < persons.Length; ++i)
         yield return persons[i];
    }
 }

·主函數測試代碼

 People people = new People(persons);
 foreach (var person in people)   // 默認
    Console.WriteLine(person.ToString());
 foreach (var person in people.IterMethod1)    // [1]
    Console.WriteLine(person.ToString());
 foreach (var person in people.IterMethod2())  // [2]
    Console.WriteLine(person.ToString());

 對於返回泛型IEnumerator<T>、IEnumerable<T>的迭代器,同理。


參考

·foreach 與 yield

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章