首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 實作 IEnumerable 與 IEnumerator:讓自訂類別支援 foreach 迭代

     編輯:關於C#

為什麼要實作 IEnumerable?

實作 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; }
}

車庫集合:手動實作 IEnumerable


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) 方法

進階:手動實作 IEnumerator (不使用 yield)


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 vs 手動實作

特性yield return手動 IEnumerator
程式碼行數極少較多
Reset 支援不支援 (拋 NotSupportedException)可完整支援
狀態管理編譯器產生狀態機手動控制
效能極佳 (編譯器優化)略優 (無狀態機)
除錯較難 (狀態機)容易

實作 IEnumerable 的完整清單

  1. 繼承 IEnumerable<T> (建議) 或 IEnumerable
  2. 實作 GetEnumerator() 回傳 IEnumerator<T>
  3. 顯式實作非泛型 IEnumerable.GetEnumerator() 回傳泛型版本
  4. 迭代器類別實作 IEnumerator<T> : IDisposable, IEnumerator
  5. Current 處理邊界條件
  6. MoveNext 正確推進邏輯
  7. Reset 重置狀態 (yield 版本不可行)
  8. 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 vs ICollection vs IList

介面功能適用場景
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 讓代碼簡潔優雅。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved