實作 foreach 可迭代物件是 C# 類別庫設計的基本功。C# 1.0 需手動實作 IEnumerable 與 IEnumerator 介面,C# 2.0 引入 yield return 大幅簡化程式碼並提供型別安全。本文以 ListBox 為例,對比兩種時代的寫法。
using System;
using System.Collections;
public class ListBox : IEnumerable
{
private string[] _items = new string[8];
private int _count = 0;
public ListBox(params string[] initial)
{
foreach (var s in initial)
if (_count < 8) _items[_count++] = s;
}
public void Add(string s)
{
if (_count < 8) _items[_count++] = s;
}
public int Count => _count;
// IEnumerable.GetEnumerator
public IEnumerator GetEnumerator()
{
return new ListBoxEnumerator(this);
}
// 巢狀類別實作 IEnumerator
private class ListBoxEnumerator : IEnumerator
{
private readonly ListBox _parent;
private int _index = -1;
public ListBoxEnumerator(ListBox parent) => _parent = parent;
public bool MoveNext()
{
_index++;
return _index < _parent._count;
}
public void Reset() => _index = -1;
public object Current
{
get
{
if (_index < 0 || _index >= _parent._count)
throw new InvalidOperationException();
return _parent._items[_index];
}
}
}
}
使用方式:
var lb = new ListBox("A", "B", "C");
lb.Add("D");
foreach (string s in lb) Console.WriteLine(s);
using System;
using System.Collections.Generic;
public class ListBox : IEnumerable<string>
{
private readonly List<string> _items = new List<string>(8);
public ListBox(params string[] initial)
{
_items.AddRange(initial);
}
public void Add(string s) => _items.Add(s);
public int Count => _items.Count;
// 單一 GetEnumerator 即可,編譯器自動產生狀態機
public IEnumerator<string> GetEnumerator()
{
foreach (var s in _items)
yield return s; // 關鍵字:產生迭代器狀態機
}
// 非泛型介面顯式實作 (相容舊代碼)
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
IEnumerator<T> 的巢狀類別,自動管理 Current、MoveNext、Reset。GetEnumerator() 不會立即執行迴圈,只有在 MoveNext() 時才逐步執行到下一個 yield return。IEnumerable<T>,foreach 直接取得強型別 T,免拆箱。public IEnumerable<string> GetItems()
{
yield return "First";
yield return "Second";
if (DateTime.Now.Second % 2 == 0)
yield return "EvenSecond";
yield return "Last";
}
public IEnumerable<int> GetNumbers(int max)
{
for (int i = 0; ; i++)
{
if (i >= max) yield break; // 結束迭代
yield return i;
}
}
| 指標 | 手動 IEnumerator | yield return |
|---|---|---|
| 程式碼行數 | ~50 行 | ~10 行 |
| 記憶體配置 | 手控 | 編譯器產生的狀態機類別 (每次迭代一個實例) |
| 延遲執行 | 需自行實作 | 內建 |
| 除錯體驗 | 可逐行中斷 | 可逐行中斷 (VS 支援) |
IEnumerable<T> 並使用 yield return。IEnumerable<T> 而非具體類別,保持介面導向。yield return 方法中混入副作用 (如修改共享狀態),因執行時機不可預期。.ToList() 物質化,避免重複計算。