實作 IEnumerable (或 IEnumerable<T>) 讓自訂類別支援 foreach、LINQ 查詢、集合初始化器等語法糖,融入 .NET 集合生態。
public class Car
{
public int Speed { get; set; }
public string PetName { get; set; }
public Car(int speed, string name) { Speed = speed; PetName = name; }
}
using System;
using System.Collections;
using System.Collections.Generic;
public class Garage : IEnumerable
{
private readonly Car[] _cars;
public Garage()
{
_cars = new Car[4];
_cars[0] = new Car(30, "Rusty");
_cars[1] = new Car(34, "Tom");
_cars[2] = new Car(45, "KIMM");
_cars[3] = new Car(50, "Fred");
}
// 泛型 GetEnumerator (foreach、LINQ 使用)
public IEnumerator GetEnumerator()
{
foreach (var car in _cars)
yield return car; // 自動產生實作 IEnumerator 的編譯器生成類別
}
// 非泛型 GetEnumerator (IEnumerable 介面顯式實作)
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
var garage = new Garage();
foreach (var car in garage)
Console.WriteLine($"{car.PetName} - {car.Speed} km/h");
// LINQ 查詢
var fastCars = garage.Where(c => c.Speed > 40).Select(c => c.PetName);
Console.WriteLine("快車: " + string.Join(", ", fastCars));
// 集合初始化器 (需實作 Add 方法)
// var garage2 = new Garage { new Car(60, "Speedy") }; // 需 Garage 有 Add(Car) 方法
public class GarageManual : IEnumerable
{
private readonly Car[] _cars;
public GarageManual() { /* 同上 */ }
public IEnumerator GetEnumerator() => new GarageEnumerator(_cars);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private class GarageEnumerator : IEnumerator
{
private readonly Car[] _cars;
private int _index = -1;
public GarageEnumerator(Car[] cars) => _cars = cars;
public Car Current => (_index >= 0 && _index < _cars.Length) ? _cars[_index]
: throw new InvalidOperationException();
object IEnumerator.Current => Current;
public bool MoveNext() => ++_index < _cars.Length;
public void Reset() => _index = -1;
public void Dispose() { }
}
}
| 特性 | yield return | 手動 IEnumerator |
|---|---|---|
| 程式碼行數 | 極少 | 較多 |
| Reset 支援 | 不支援 (拋 NotSupportedException) | 可完整支援 |
| 狀態管理 | 編譯器產生狀態機 | 手動控制 |
| 效能 | 極佳 (編譯器優化) | 略優 (無狀態機) |
| 除錯 | 較難 (狀態機) | 容易 |
IEnumerable<T> (建議) 或 IEnumerableGetEnumerator() 回傳 IEnumerator<T>IEnumerable.GetEnumerator() 回傳泛型版本IEnumerator<T> : IDisposable, IEnumeratorCurrent 處理邊界條件MoveNext 正確推進邏輯Reset 重置狀態 (yield 版本不可行)Dispose 釋放資源IEnumerable.GetEnumerator() → 編譯錯誤 (介面衝突)yield return 在 try-finally 中使用 → finally 會在 Dispose 時執行,需注意InvalidOperationException (集合版本號改變)
public class Garage : IEnumerable
{
private readonly List _cars = new();
public void Add(Car car) => _cars.Add(car); // 關鍵:Add 方法
public IEnumerator GetEnumerator() => _cars.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
// 現在支援
var garage = new Garage
{
new Car(30, "Rusty"),
new Car(45, "KIMM")
};
| 介面 | 功能 | 適用場景 |
|---|---|---|
IEnumerable<T> | 唯讀迭代 | 資料源、查詢結果、串流資料 |
ICollection<T> | + Count、Add、Remove、Clear、Contains、CopyTo | 可修改集合、需長度資訊 |
IList<T> | + 索引存取、Insert、RemoveAt、IndexOf | 有序、隨機存取集合 |
IReadOnlyCollection<T> | 唯讀 + Count | 公開內部集合防外部修改 |
IReadOnlyList<T> | 唯讀 + Count + 索引 | 公開有序唯讀集合 |
實作 IEnumerable<T> 是讓自訂類別成為「一等公民」集合的第一步,配合 yield return 讓代碼簡潔優雅。