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

C# 實作自訂集合類別:IEnumerable 與 IEnumerator 完整教學

     編輯:關於C語言

為什麼要實作自訂集合?

.NET 內建 List<T>、Dictionary<TKey,TValue>、HashSet<T> 等集合已涵蓋 95% 場景。但特定業務需求可能需要:

  • 特定新增/移除規則 (如不可重複、自動排序、容量限制)
  • 封裝內部儲存結構 (資料庫、檔案、網路串流)
  • 提供領域特有語意介面 (如 OrderBook.AddBid() vs List.Add())
  • 實作 foreach 支援,讓呼叫端程式碼更自然

核心介面:IEnumerable 與 IEnumerator

介面角色關鍵成員泛型版本
IEnumerable可列舉物件 (容器)GetEnumerator()IEnumerable<T>
IEnumerator列舉器 (游標)MoveNext(), Reset(), CurrentIEnumerator<T>

完整實作範例:書名集合 Books

1. 非泛型版本 (教學基礎)

using System;
using System.Collections;
using System.Collections.Generic;

public class Books : IEnumerable // 非泛型
{
    private readonly ArrayList _titles = new ArrayList();

    public void Add(string title) => _titles.Add(title);
    public void Remove(string title) => _titles.Remove(title);
    public int Count => _titles.Count;

    // 必須實作:回傳列舉器
    public IEnumerator GetEnumerator() => new BooksEnumerator(this);
}

// 獨立列舉器類別
public class BooksEnumerator : IEnumerator
{
    private readonly Books _books;
    private int _position = -1; // 游標初始位置

    public BooksEnumerator(Books books) => _books = books;

    public bool MoveNext()
    {
        if (_position < _books.Count - 1)
        {
            _position++;
            return true;
        }
        return false;
    }

    public void Reset() => _position = -1;

    public object Current
    {
        get
        {
            if (_position == -1 || _position >= _books.Count)
                throw new InvalidOperationException("列舉器位置無效");
            return _books._titles[_position];
        }
    }
}

// 使用範例
Books books = new Books();
books.Add("C# in Depth");
books.Add("CLR via C#");
books.Add("Effective C#");

foreach (string title in books) // 編譯器自動呼叫 GetEnumerator/MoveNext/Current
{
    Console.WriteLine(title);
}

2. 泛型版本 (現代標準,強型別、效能更好)

public class Books : IEnumerable // 泛型介面
{
    private readonly List _titles = new List();

    public void Add(string title) => _titles.Add(title);
    public void Remove(string title) => _titles.Remove(title);
    public int Count => _titles.Count;

    // 泛型 GetEnumerator
    public IEnumerator GetEnumerator() => new BooksEnumerator(this);
    
    // 非泛型介面顯式實作 (相容性)
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class BooksEnumerator : IEnumerator
{
    private readonly Books _books;
    private int _position = -1;

    public BooksEnumerator(Books books) => _books = books;

    public string Current => _books._titles[_position]; // 強型別 Current

    object IEnumerator.Current => Current; // 非泛型介面顯式實作

    public bool MoveNext()
    {
        if (_position < _books.Count - 1)
        {
            _position++;
            return true;
        }
        return false;
    }

    public void Reset() => _position = -1;

    public void Dispose() { } // 通常無非托管資源,保留空實作
}

3. 使用迭代器語法糖 (C# 2.0+,最推薦)

public class Books : IEnumerable
{
    private readonly List _titles = new List();

    public void Add(string title) => _titles.Add(title);
    public void Remove(string title) => _titles.Remove(title);
    public int Count => _titles.Count;

    // yield return 自動生成列舉器類別 (編譯器魔法)
    public IEnumerator GetEnumerator()
    {
        foreach (var title in _titles)
        {
            yield return title; // 狀態機自動管理 _position
        }
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

迭代器進階模式

條件過濾

public IEnumerable GetTitlesStartingWith(char prefix)
{
    foreach (var title in _titles)
    {
        if (title.StartsWith(prefix.ToString(), StringComparison.OrdinalIgnoreCase))
            yield return title;
    }
}

延遲執行特性演示

var books = new Books();
books.Add("A");
books.Add("B");

var query = books.GetTitlesStartingWith('A'); // 此時尚未執行任何迴圈!
Console.WriteLine("查詢已建立,未執行");

foreach (var title in query) // 這裡才真正開始迭代
{
    Console.WriteLine(title);
}

實作 IList / ICollection 獲得完整集合功能

若需索引存取、插入、刪除、複製等完整集合行為,建議繼承 Collection<T> 或實作 IList<T>:

using System.Collections.ObjectModel;

public class Books : Collection // 繼承 Collection 即獲得 IList, ICollection, IEnumerable
{
    // 可覆寫受保護方法自訂行為
    protected override void InsertItem(int index, string item)
    {
        if (string.IsNullOrWhiteSpace(item))
            throw new ArgumentException("書名不可為空");
        base.InsertItem(index, item);
    }

    protected override void SetItem(int index, string item)
    {
        if (string.IsNullOrWhiteSpace(item))
            throw new ArgumentException("書名不可為空");
        base.SetItem(index, item);
    }
}

效能與設計指導原則

原則說明
優先使用 yield return程式碼簡潔、編譯器生成最佳化狀態機、自動實作 IDisposable
回傳 IEnumerable<T> 而非 List<T>保護封裝、支援延遲執行、允許回傳陣列/迭代器/資料庫查詢
避免在迭代器中修改集合會導致 InvalidOperationException (集合已修改),改用 ToList() 先快照
大量資料考慮 IAsyncEnumerable<T>.NET Core 3.0+ 支援非同步串流,避免阻塞執行緒
實作 IReadOnlyCollection<T> / IReadOnlyList<T>唯讀語意明確,防止呼叫端誤修改

總結

  1. IEnumerable/IEnumerator 是 foreach 的合約基礎。
  2. yield return 是現代 C# 實作迭代器的標準方式,簡潔且高效。
  3. 繼承 Collection<T> 可快速獲得完整集合功能並保有擴充點。
  4. 介面導向設計 (IEnumerable<T>、IReadOnlyList<T>) 提升彈性與可測試性。

掌握自訂集合實作,能讓領域模型更豐富、API 更具表達力、封裝更嚴謹。

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