2014年1月18日 星期六

C# - Iterator反覆運算器

在Design Pattern中,Iterator Pattern相信大家都很熟悉,而在C#中,使用foreach語法本身就可以算是在使用Iterator Pattern了。在.Net支援的資料結構裡,大部份都會支援foreach語法,不過有時候因為需求,會自己實作其它的資料結構,本身文章將會說明如何讓自己設計的類別支援foreach語法。

下面是微軟官方對Iterator的定義:
Iterator 是一種方法、get 存取子 (Accessor),或是使用 yield 關鍵字對陣列或集合類別執行自訂反覆運算法的運算子。yield 傳回陳述式 (Statement) 會使來源序列 (Sequence) 中的項目,在即將存取來源序列中的下一個項目時傳回到呼叫端。雖然您可以將 Iterator 撰寫為方法,編譯器 (Compiler) 卻會將其轉譯為巢狀類別,也就是實際上的狀態機器。只要用戶端程式碼上的 foreach 迴圈持續進行,這個類別就會持續追蹤 Iterator 的位置。


Iterator 的特性:
Iterator 是程式碼區段,會傳回相同型別之按順序排列的值。
Iterator 可以當做方法主體、運算子或 get 存取子使用。
Iterator 程式碼會使用 yield return 陳述式輪流傳回各元素。yield break 則會結束反覆運算。
可在類別上實作多個 Iterator。每個 Iterator 必須像任何類別成員一樣擁有唯一名稱,且可以由 foreach 陳述式中的用戶端程式碼叫用,如下所示:foreach(int x in SampleClass.Iterator2){}。
Iterator 的傳回型別必須是 IEnumerable、IEnumerator、IEnumerable<T> 或 IEnumerator<T>。
Iterators 是 LINQ 查詢中延後執行行為的基礎。
yield 關鍵字會用來指定所傳回的單一個或多個值。當到達 yield return 陳述式時,便會儲存目前的位置。下一次呼叫此 Iterator 時,便會從這個位置重新開始執行。
Iterator 特別適合與集合類別搭配使用,因為能夠提供逐一查看像是二元樹等複雜資料結構的方法。

現在假設要從資料庫撈Person資料表的資料,欄位有age跟name並且包成Person物件,Person類別的設計如下:

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

public Person(){}
}

再來實作資料結構MyList,用來放Person物件。
首先引用System.Collections這個命名空間。

using System.Collections;

MyList的程式碼如下,特別注意的是MyList要繼承IEnumerable<>跟IEnumerable都不能少。


在Design Pattern中,Iterator Pattern相信大家都很熟悉,而在C#中,使用foreach語法本身就可以算是在使用Iterator Pattern了。在.Net支援的資料結構裡,大部份都會支援foreach語法,不過有時候因為需求,會自己實作其它的資料結構,本身文章將會說明如何讓自己設計的類別支援foreach語法。

現在假設要從資料庫撈Person資料表的資料,欄位有age跟name並且包成Person物件,Person類別的設計如下:


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

public Person(){}
}

再來實作資料結構MyList,用來放Person物件。
首先引用System.Collections這個命名空間。

using System.Collections;

MyList的程式碼如下,特別注意的是MyList要繼承IEnumerable<>跟IEnumerable

public class MyList<T>: IEnumerable<T>, IEnumerable where T:Person
{
public MyList(int size)
{
this.MyResources = new T[size];
}

public T this[int index]
{
get
{
return this.MyResources[index];
}

set
{
this.MyResources[index] = value;
}
}

private T[] MyResources;

IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
for(int i = 0; i< this.MyResources.Count(); i++)
{
yield return this.MyResources[i];
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)((IEnumerable<T>)this).GetEnumerator();
}
}

在IEnumerable<T>.GetEnumerator中,有使用到yield這個關鍵字,可以很輕易的篩選要回傳的元素,本範例為了方便說明,所以是回傳所有的元素。這是C# 2.0開始才有的。

MyList<Person>testList = new MyList<Person>(4);
testList[0] = new Person();
testList[0].age = 10;
testList[0].name = "Peter";
testList[1] = new Person();
testList[1].age = 10;
testList[1].name = "John";
testList[2] = new Person();
testList[2].age = 10;
testList[2].name = "Jobs";
testList[3] = new Person();
testList[3].age = 10;
testList[3].name = "Pages";


也可以做一些特別的篩選動作:
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
for(int i = 0; i< this.MyResources.Count(); i +=2)
{
yield return this.MyResources[i];
}

for(int i=1; i<this.MyResources.Count();i+=2)
{
yield return this.MyResources[i];
}
}

上面的方法,是讓偶數位的元素先產生,然後是奇數位的。
這樣的寫法,也可以用在LINQ上:
var result = from p in testList where p.age == 12 select p;

foreach(Person item in result)
{
Console.WriteLine("Age:{0}, Name:{1}", item.age, item.name);
}

-雲遊山水為知已逍遙一生而忘齡- 電腦神手

沒有留言:

張貼留言