IEnumerable & IEnumerator介面的實作
實作IEnumerable & IEnumerator介面最主要的好處是該類別能被foreach直接遍巡處理,有鑒於網路上存在許多IEnulerable & IEnumerator介面的實作方式跟個人的理解有所出入,這邊將個人的理解稍作了整理。
IEnumerator介面的組成成員如下,內含有Current屬性與MoveNext、Reset兩個方法。
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
其中Current成員方法可用以取得目前遍巡到的項目。在MSDN上有清楚的提到當列舉索引在第一個項目之前,或是最後一個項目之後,也就是說當列舉索引是不合法時會拋出InvalidOperationException例外。
因此在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 < _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);
}
}