關於在迭代器模式的實作上,C#已提供了很方便的解決方案,
只要實作IEnumerator與IEnumerable兩個介面,即可實現此模式,
若使用C#的語法糖"yield",程式會變得更簡潔.
本文會對IEnumerator、IEnumerable還有yield進行範例的講解.
===========================================================================
迭代器模式通常都會搭配foreach使用,
若該資料結構要使用foreach,需繼承自IEnumerable,
我們常用String、Array與List,可使用foreach取出內部的資料,
是因為都有實作IEnumerable這個介面.
List<int> nums = new List<int> { 1, 2, 3, 4, 5 }; //建立List
foreach (int i in nums) //使用foreach取出所有的數值
{
Console.WriteLine(i);
}
/*result:
* 1
* 2
* 3
* 4
* 5
*/
===========================================================================
C#在實現迭代器模式時,必要實作IEnumerator與IEnumerable這兩個介面
foreach會呼叫IEnumerator介面中的GetEnumerator()
此方法回傳IEnumerator型別,
而IEnumerator包含了MoveNext(),Reset()與Current三個方法與屬性
實作這三個方法與屬性即可讓foreach正常工作,
實作IEnumerator的類別,使用foreach不僅能遍歷資料,也可依據需求,輸出特定條件的資料.
本範例會利用foreach,列印出Student類別的陣列中大於18歲的資料.
首先建立Student類別,後面範例會使用foreach來找尋此類別的資料.
public class Student
{
public int Age { get; set; }
public string Name { get; set; }
public int Student_ID { get; set; }
public override string ToString() //複寫ToString方便最後給Console.WriteLine()呈現資料
{
return $"name= {Name} age= {Age} Student_ID= {Student_ID,0:D3}";
}
}
建立ClassStudentEnum類別,此類別要實作IEnumerator,
此類別會被IEnumerable的GetEnumerator()進行調用,
在IEnumerator有三個成員需要實作,皆由foreach調用:
1. Current: 返回搜尋的結果,本範例為返回Student類別
2. MoveNext(): 返回bool,確認是否還有資料需要輸出
3. Reset(): 設定列舉的初始位置
由於本範例是使用泛型的IEnumerator,
故要實作Dispose(),此方法用於釋放資源.
在本範例中只要顯示18歲以上學生的資料,在MoveNext()中篩選資料
只有在找到大於18歲以上時,才會返回True,
讓Current可取得正確的資料.
public class ClassStudentEnum : IEnumerator<Student>
{
private Student[] _students;
private int index=-1;
public ClassStudentEnum(Student[] students)
{
_students = students; //取得Student[]
}
public Student Current //返回搜尋的結果
{
get
{
return _students[index];
}
}
object IEnumerator.Current => Current;
public void Dispose() //釋放資源
{
_students = null;
}
public bool MoveNext() //確認是否還有資料需要輸出
{
while (index < _students.Length-1)
{
index++;
if (_students[index].Age >= 18) //輸出大於18歲以上的學生資料
return true;
}
return false;
}
public void Reset() //設定列舉的初始位置
{
index = -1;
}
}
再來建立ClassStudent實作IEnumerable,
讓foreach可透過GetEnumerator()取得實作的IEnumerator.
public class ClassStudent : IEnumerable<Student>
{
private ClassStudentEnum _classStudentEnum;
public ClassStudent(Student[] students)
{
_classStudentEnum = new ClassStudentEnum(students); //產生IEnumerator的物件
}
public IEnumerator<Student> GetEnumerator() //此方法會被foreach調用,返回IEnumerator
{
return _classStudentEnum;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
最後在Main建立Student的陣列,並建立列舉,
使用foreach進行調用,即可得到結果,
本範例由foreach顯示出Student陣列中,
大於18歲以上,學生的資料.
static void Main(string[] args)
{
Student[] students = new Student[] //建立student的陣列
{
new Student{Age=15,Name="Ben",Student_ID=001},
new Student{Age=20,Name="Danny",Student_ID=002},
new Student{Age=25,Name="Lucia",Student_ID=003},
new Student{Age=18,Name="Daisy",Student_ID=004},
new Student{Age=12,Name="Rob",Student_ID=005},
new Student{Age=17,Name="Teddy",Student_ID=006},
new Student{Age=22,Name="Jill",Student_ID=007}
};
ClassStudent classStudent = new ClassStudent(students); //將students放入列舉
foreach (Student student in classStudent) //foreach調用IEnumerable與IEnumerator
{
Console.WriteLine(student);
}
/* result:
* name= Danny age= 20 Student_ID= 002
* name= Lucia age= 25 Student_ID= 003
* name= Daisy age= 18 Student_ID= 004
* name= Jill age= 22 Student_ID= 007
*/
}
===========================================================================
接下來,示範另一個C#的語法糖yield,
這顆糖啊~讓迭代器模式的實作又更簡單了,
本節仍使用上面的範例,並用yield進行實作
搜尋Student類別的陣列,顯示大於18歲學生的資料,
建立靜態方法IteratorMethod(),返回值為IEnumerable,
搜尋Student陣列的資料,
當學生大於18歲,利用yield return返回資料
public class Student_yield
{
public static IEnumerable<Student> IteratorMethod(Student[] students) //回傳IEnumerable,並實作yield return
{
for (int i = 0; i < students.Length; i++)
{
if (students[i].Age >= 18) //輸出大於18歲的學生資料
yield return students[i];
}
}
}
然後在Main一樣建立Student的陣列,
就可利用foreach返回Student陣列中,大於18歲的學生資料.
static void Main(string[] args)
{
Student[] students = new Student[] //建立student的陣列
{
new Student{Age=15,Name="Ben",Student_ID=001},
new Student{Age=20,Name="Danny",Student_ID=002},
new Student{Age=25,Name="Lucia",Student_ID=003},
new Student{Age=18,Name="Daisy",Student_ID=004},
new Student{Age=12,Name="Rob",Student_ID=005},
new Student{Age=17,Name="Teddy",Student_ID=006},
new Student{Age=22,Name="Jill",Student_ID=007}
};
foreach (Student student in Student_yield.IteratorMethod(students)) //由foreach調用Student_yield.IteratorMethod()
{
Console.WriteLine(student); //顯示yield return返回的資料
}
/* Result:
* name= Danny age= 20 Student_ID= 002
* name= Lucia age= 25 Student_ID= 003
* name= Daisy age= 18 Student_ID= 004
* name= Jill age= 22 Student_ID= 007
*/
}
關於yield本篇文章僅講解部分功能,
yield不僅能傳回IEnumerable,也能進行非同步的調用,
===========================================================================
有完整的程式碼,包含2個專案:
C Sharp Iterator Pattern Exercise: 使用IEnumerable與IEnumerator實現
C Sharp yield Exercise: 使用yield實現
===========================================================================