簡介
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");
// 處理當前頁
}