IEnumerable & IEnumerator介面的實作


實作IEnumerable & IEnumerator介面最主要的好處是該類別能被foreach直接遍巡處理,有鑒於網路上存在許多IEnulerable & IEnumerator介面的實作方式跟個人的理解有所出入,這邊將個人的理解稍作了整理。



IEnumerator介面的組成成員如下,內含有Current屬性與MoveNext、Reset兩個方法。



public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}



其中Current成員方法可用以取得目前遍巡到的項目。在MSDN上有清楚的提到當列舉索引在第一個項目之前,或是最後一個項目之後,也就是說當列舉索引是不合法時會拋出InvalidOperationException例外。


image



因此在MSDN的範例中會像下面這樣撰寫,直接將索引位置的資料回傳,並利用try/catch欄截索引錯誤的例外,當例外發生時改丟InvalidOperationException例外。



public object Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}



MoveNext方法可將索引位置往前推至下個項目,並回傳是否還有可遍巡的元素。



public bool MoveNext()
{
position++;
return (position < _people.Length);
}



Reset方法則是將索引位置移回到遍巡開始前,也就是回到第一個元素前。



public void Reset()
{
position = -1;
}



IEnumerable介面的組成成員如下,只有內含一個GetEnumerator方法。



public interface IEnumerable
{
IEnumerator GetEnumerator();
}



GetEnumerator方法呼叫後會回傳IEnumerator介面的物件實體,在該方法的實作常常會被誤用,舉個例子來說,有的教學會直接將類別同時實作IEnumerable與IEnumerator這兩個介面,然後實作該方法時直接像是下面這樣:



public IEnumerator GetEnumerator()
{
return this;
}



這樣的作法會有問題,因為遍巡完後索引並不會自動再次初始,故當第二次遍巡時就會發生異常。且這時物件的實體也只有一份,索引也只有一份,同時間給不同的執行緒去遍巡可能也會發生錯亂的情況。



這樣的問題若有注意到MSDN的範例寫法,其實會發現MSDN上的範例已經避開了。MSDN的範例兩個介面是用不同類別去實作,PeopleEnum為列舉People用的迭代器類別,透過People.GetEnumerator可以取得PeopleEnum的迭代器物件實體,該物件實體每次叫用GetEnumerator都會去產生,故皆為不同的物件實體,且各自含有各自的索引,故可免除上述的問題。



public IEnumerator GetEnumerator()
{
return new PeopleEnum(_people);
}



若想要將兩個介面實作在同一個類別上,我們也可以參考MSDN的範例,開ㄧ個建構子讓GetEnumerator方法產生物件副本回傳。



public class PersonCollection : IEnumerable,IEnumerator
{

public PersonCollection(Person[] list)
{
_peoples = list;
}

public IEnumerator GetEnumerator()
{
return new PersonCollection(_peoples);
}
}



完整範例如下:



{
static void Main(string[] args)
{

        PersonCollection persons = new PersonCollection ( new Person[]{new Person("Larry")});
        foreach (Person person in persons)
        {
            Console.WriteLine(person.Name);
        }
        foreach (Person person in persons)
        {
            Console.WriteLine(person.Name);
        } 
    }
}

  public class Person {
      public string Name { get; set; }

      public Person(string name)
      {
          this.Name = name;
      }
}


public class PersonCollection : IEnumerable,IEnumerator 
{
    private Person[] _peoples;
    private int position = -1;

    public PersonCollection(Person[] list)
    {
        _peoples = list;
    }

    public bool MoveNext()
    {
        position++;
        return (position &lt; _peoples.Length);
    }

    public void Reset()
    {
        position = -1;
    }

    public object Current
    {
        get
        {
            try
            {
                return _peoples[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }

    public IEnumerator GetEnumerator()
    {
        return new PersonCollection(_peoples);
    }
}</pre>



Link



  • When IEnumerator.Reset() method is called ? - Stack Overflow


  • IEnumerable 介面


  • IEnumerator 介面