作者:烏龍哈里
時間:2015-10-24
平台:Window7 64bit,Visual Studio Community 2015
本文參考:
本文章節:
- 接口IEnumerable實現
- 接口IEnumerable<T>實現
正文:
本文是作者摸索學習.Net的過程,逐步進行,比較繁瑣,是作者本人用來幫助記憶的博文。
我們先去看看公開的.Net4.0的源程序中IEnumerable<T>、IEnumerable、IEnumerator<T>和IEnumerator這四個接口是如何聲明的:
public interface IEnumerable<out T> : IEnumerable
{
new IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
new T Current {
get;
}
}
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerator
{
bool MoveNext();
Object Current {
get;
}
void Reset();
}
一、接口IEnumerable實現
1、建一個學生數據結構和一個學生集合類:
//student數據結構
class Student
{
public int id;
public string name;
}
//student 集合
class StudentCollection
{
public List<Student> students = new List<Student>();
public void Add(Student student)
{
students.Add(student);
}
}
公開一個Add()方法以添加數據,我們的集合類建立完畢。下來添加數據:
static void Main(string[] args)
{
StudentCollection sc = new StudentCollection();
sc.Add(new Student { id=0,name="Tony"});
sc.Add(new Student { id=1,name="Micheal"});
sc.Add(new Student { id =2, name = "Amy" });
foreach(var s in sc) {...}
}
}
當我們想用foreach()遍歷的時候,編譯器會告訴我們StudentCollection不包含GetEnumerator,不能用foreach遍歷。雖然StudentCollection里面有能用遍歷的List<T>,但我們不想在屬性上迭代,我們想在類上迭代,不能 foreach(var s in sc.students){...}
現在只有把我們的StudentCollection類改造成能foreach的。
2、繼承接口IEnumerable:
當我們在類后面加上:IEnumerable后,Visual Studio IDE會冒出來一個小黃燈泡,點進去有提示自動填充接口的約定,我們選第一項實現接口(Visaul Studio是全世界最貼心的IDE!),IDE會幫我們把SudentCollection改造成以下的:
class StudentCollection:IEnumerable
{
public List<Student> students = new List<Student>();
public void Add(Student student)
{
students.Add(student);
}
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
}
加了一個返回迭代器的方法GetEnumrator。下來按照IEnumetator接口的約定來實現我們的迭代器StudentCollectionEnumerator,用IDE自動補全代碼如下:
//迭代器
class StudentCollectionEnumerator : IEnumerator
{
public object Current
{
get
{
throw new NotImplementedException();
}
}
public bool MoveNext()
{
throw new NotImplementedException();
}
public void Reset()
{
throw new NotImplementedException();
}
}
我的理解是:Current返回當前元素,MoveNext移動到下一個,Reset回到第一個元素。但根據MSDN上面的說法,Reset 方法提供的 COM 互操作性。它不一定需要實現;相反,實施者只需拋出NotSupportedException。但是,如果您選擇執行此操作,則應確保沒有調用方依賴於Reset功能。
迭代器工作的原理是:先調用MoveNext()方法,然后讀取Current得到元素,直到MoveNext返回false。
我們需要3個字段分別放置 元素的位置、元素、元素集。改變后的程序如下:
//迭代器
class StudentCollectionEnumerator : IEnumerator
{
private int _index;
private List<Student> _collection;
private Student value;
public StudentCollectionEnumerator(List<Student> colletion)
{
_collection = colletion;
_index = -1;
}
object IEnumerator.Current
{
get { return value; }
}
public bool MoveNext()
{
_index++;
if (_index >= _collection.Count) { return false; }
else { value = _collection[_index]; }
return true;
}
public void Reset()
{
_index = -1;
}
}
首先,迭代器初始化,引入元素集 _collection,並把索引 _index設置成-1。設置成-1而不是0是因為迭代器首先調用MoveNext,在MoveNext里面我們先把索引+1指向下一個元素,如果索引_index的值初始為0,則第一個元素是元素集[1],第二個元素了。
其次,我們要把object Current改成 IEnumerator.Current,這個是實現迭代器的關鍵。返回元素。(好像有裝箱的行為)
第三,在MoveNext方法內累加索引,並從元素集中讀取元素。然后讓索引值超出元素集返回個false值。
最后,在Reset方法內讓索引值為-1,不過好像直接拋出錯誤也成。
迭代器寫好了,我們在StudentColletion類里面調用:
class StudentCollection : IEnumerable
{
public List students;
public StudentCollection()
{
students = new List();
}
public void Add(Student student)
{
students.Add(student);
}
public IEnumerator GetEnumerator()
{
return new StudentCollectionEnumerator(students);
}
}
測試運行一下,大功告成!我們實現了可枚舉的自己的類。
通過觀察,發現迭代器主要就是返回一個元素對象,而StudentColletion里面的students元素集是List的,本身就能枚舉,我們能不能利用這個不用專門寫迭代器來實現枚舉呢?
答案是肯定的,我們這樣寫:
class StudentCollection:IEnumerable
{
public List<Student> students = new List<Student>();
public void Add(Student student)
{
students.Add(student);
}
public IEnumerator GetEnumerator()
{
foreach(var s in students)
{
yield return s;
}
}
}
這樣就能實現枚舉了,真簡單,充分利用了.Net給出的各種可枚舉集合,不用再去寫GetEnumerator這種累活了。
二、接口IEnumerable<T>實現
如果我們想寫一個通用的可foreach的類,用到泛型:
class MyCollection<T>
{
public List<T> mycollection = new List<T>();
public void Add(T value)
{
mycollection.Add(value);
}
}
其實這個MyCollection類只不過是在List<T>外面封裝了一層,要實現IEnumable<T>,繼承該泛型接口,Visual Studio 的IDE自動幫我們補全后,如下:
class MyCollection:IEnumerable
{
public List mycollection = new List();
public void Add(T value)
{
mycollection.Add(value);
}
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
我們直接用上面第二個簡單的寫法,改成:
class MyCollection:IEnumerable
{
public List mycollection = new List();
public void Add(T value)
{
mycollection.Add(value);
}
public IEnumerator GetEnumerator()
{
foreach(var s in mycollection)
{
yield return s;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
foreach (var s in mycollection)
{
yield return s;
}
}
}
測試運行:
static void Main(string[] args)
{
MyCollection mc = new MyCollection();
mc.Add(0);
mc.Add(1);
mc.Add(2);
foreach(var s in mc) { Console.WriteLine(s); }
Console.ReadKey();
}
大功告成!
雖然第二種寫法比較投機,充分利用了.NET Framework給的各種泛型集合可枚舉的特征。不過我們也自己實現了一個GetEnumerator(),了解了枚舉器的工作原理。本章學習目的達成。
