博客 / 詳情

返回

深入理解 C#.NET IEnumerable<T>:一切集合的起點

簡介

IEnumerable<T>.NET 中最核心的接口之一,位於 System.Collections.Generic 命名空間中。它代表一個可枚舉的集合,支持在集合上進行迭代操作。

IEnumerable<T> 是什麼?

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}
  • 它定義了一個可枚舉對象的契約;
  • 任何實現了 IEnumerable<T> 的類型都能被 foreach 循環遍歷;
  • 泛型版 IEnumerable<T> 是非泛型 IEnumerable 的類型安全擴展;
  • 它的核心方法只有一個:GetEnumerator()

IEnumerable 與 IEnumerator 的關係

要理解 IEnumerable<T>,必須知道它依賴另一個接口:

public interface IEnumerator<out T> : IDisposable, IEnumerator
{
    T Current { get; }
    bool MoveNext();
    void Reset(); // 通常不實現
}

關係示意圖:

IEnumerable<T>
    └── GetEnumerator()
          └── 返回 IEnumerator<T>
                  ├── MoveNext() → 是否還有元素
                  ├── Current → 當前元素
                  └── Reset() → 重置(可選)

基本用法

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> fruits = new List<string> { "Apple", "Banana", "Cherry" };
        
        // 使用 foreach 遍歷(推薦)
        foreach (string fruit in fruits)
        {
            Console.WriteLine(fruit);
        }
        
        // 等價的手動迭代方式
        IEnumerator<string> enumerator = fruits.GetEnumerator();
        try
        {
            while (enumerator.MoveNext())
            {
                string fruit = enumerator.Current;
                Console.WriteLine(fruit);
            }
        }
        finally
        {
            enumerator?.Dispose();
        }
    }
}

手寫一個 IEnumerable<T> 示例

public class NumberCollection : IEnumerable<int>
{
    private readonly int[] _numbers = { 1, 2, 3, 4, 5 };

    public IEnumerator<int> GetEnumerator()
    {
        foreach (var n in _numbers)
            yield return n;
    }

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

使用:

var numbers = new NumberCollection();
foreach (var n in numbers)
{
    Console.WriteLine(n);
}

輸出:

1
2
3
4
5

yield return 的魔法

等價於下面的展開版:

public IEnumerator<int> GetEnumerator()
{
    return new Enumerator();
}

private class Enumerator : IEnumerator<int>
{
    private int _index = -1;
    private readonly int[] _numbers = { 1, 2, 3, 4, 5 };

    public int Current => _numbers[_index];
    object IEnumerator.Current => Current;

    public bool MoveNext()
    {
        _index++;
        return _index < _numbers.Length;
    }

    public void Reset() => _index = -1;
    public void Dispose() { }
}

yield 編譯後自動生成狀態機類,就是這種效果。

這也是 LINQ 延遲執行的基礎機制。

foreach 的底層原理

當寫:

foreach (var item in collection)
{
    Console.WriteLine(item);
}

編譯器實際上生成:

using (var enumerator = collection.GetEnumerator())
{
    while (enumerator.MoveNext())
    {
        var item = enumerator.Current;
        Console.WriteLine(item);
    }
}

延遲執行(Lazy Evaluation)

LINQ 查詢(Where, Select 等)通常返回 IEnumerable<T>
這些操作是延遲執行的:只有在遍歷時才真正運行。

var numbers = new[] { 1, 2, 3, 4, 5 };

var query = numbers.Where(n => n > 2).Select(n => n * 10);

Console.WriteLine("Query created.");
foreach (var n in query)
{
    Console.WriteLine(n);
}

Where / Select 只是定義查詢,不會立即執行。
直到 foreach 時,才會真正迭代並執行邏輯。

常見實現 IEnumerable<T> 的類型

類型 説明
List<T> 基於數組實現的集合
T[] 數組
Dictionary<TKey,TValue> 鍵值對集合(枚舉鍵值對)
HashSet<T> 不重複元素集合
Queue<T> / Stack<T> 隊列與棧
string 實現了非泛型 IEnumerable<char>
LINQ 查詢結果 延遲執行序列

手寫一個支持過濾的 IEnumerable<T>

public class FilteredCollection<T> : IEnumerable<T>
{
    private readonly IEnumerable<T> _source;
    private readonly Func<T, bool> _predicate;

    public FilteredCollection(IEnumerable<T> source, Func<T, bool> predicate)
    {
        _source = source;
        _predicate = predicate;
    }

    public IEnumerator<T> GetEnumerator()
    {
        foreach (var item in _source)
        {
            if (_predicate(item))
                yield return item;
        }
    }

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

使用:

var list = new List<int> { 1, 2, 3, 4, 5 };
var filtered = new FilteredCollection<int>(list, x => x % 2 == 0);

foreach (var n in filtered)
    Console.WriteLine(n);

IEnumerable<T> vs IQueryable<T>

特性 IEnumerable IQueryable
執行時機 本地內存中 可翻譯為遠程查詢(如 SQL)
適用場景 內存集合 數據庫 ORM(EF Core 等)
表達式類型 委託(Func) 表達式樹(Expression)
可延遲執行
例子 List<T>, Array, yield return DbSet<T>

示例:

// IEnumerable:在內存中過濾
var result1 = list.Where(x => x > 10);

// IQueryable:生成 SQL 查詢
var result2 = db.Users.Where(x => x.Age > 10);

IEnumerable 的擴展方法分類(LINQ 常用)

分類 示例方法
過濾 Where, Distinct, Skip, Take
投影 Select, SelectMany
聚合 Count, Sum, Average, Aggregate
元素 First, Last, Single, ElementAt
組合 Concat, Union, Intersect, Except
排序 OrderBy, ThenBy, Reverse
轉換 ToList, ToArray, ToDictionary

高級特性

自定義 LINQ 擴展方法

public static class MyLinqExtensions
{
    // 自定義 Where 方法
    public static IEnumerable<T> Where<T>(
        this IEnumerable<T> source, 
        Func<T, bool> predicate)
    {
        foreach (T item in source)
        {
            if (predicate(item))
            {
                yield return item;
            }
        }
    }
    
    // 自定義 Select 方法
    public static IEnumerable<TResult> Select<TSource, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, TResult> selector)
    {
        foreach (TSource item in source)
        {
            yield return selector(item);
        }
    }
    
    // 自定義擴展方法
    public static IEnumerable<T> SkipEveryOther<T>(this IEnumerable<T> source)
    {
        bool take = true;
        foreach (T item in source)
        {
            if (take)
            {
                yield return item;
            }
            take = !take;
        }
    }
    
    // 帶索引的擴展方法
    public static IEnumerable<TResult> SelectWithIndex<TSource, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, int, TResult> selector)
    {
        int index = 0;
        foreach (TSource item in source)
        {
            yield return selector(item, index);
            index++;
        }
    }
}

// 使用自定義擴展方法
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
var result = numbers.SkipEveryOther(); // 返回 1, 3, 5, 7

var indexed = numbers.SelectWithIndex((num, idx) => $"Index {idx}: {num}");

無限序列

public static class InfiniteSequences
{
    // 無限數字序列
    public static IEnumerable<int> InfiniteNumbers()
    {
        int i = 0;
        while (true)
        {
            yield return i++;
        }
    }
    
    // 斐波那契數列
    public static IEnumerable<long> Fibonacci()
    {
        long a = 0, b = 1;
        while (true)
        {
            yield return a;
            long temp = a;
            a = b;
            b = temp + b;
        }
    }
    
    // 隨機數序列
    public static IEnumerable<int> RandomNumbers(int min, int max)
    {
        Random rnd = new Random();
        while (true)
        {
            yield return rnd.Next(min, max);
        }
    }
}

// 使用無限序列(一定要結合 Take 等方法使用)
var firstTenFibonacci = Fibonacci().Take(10);
var randomNumbers = RandomNumbers(1, 100).Take(5);

數據分頁

public static class PagingExtensions
{
    public static IEnumerable<IEnumerable<T>> Page<T>(this IEnumerable<T> source, int pageSize)
    {
        var page = new List<T>(pageSize);
        foreach (T item in source)
        {
            page.Add(item);
            if (page.Count == pageSize)
            {
                yield return page;
                page = new List<T>(pageSize);
            }
        }
        
        if (page.Count > 0)
        {
            yield return page;
        }
    }
}

// 使用分頁
var bigCollection = Enumerable.Range(1, 1000);
foreach (var page in bigCollection.Page(100))
{
    Console.WriteLine($"Page with {page.Count()} items");
    // 處理當前頁
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.