C# IEnumerable 与 IEnumerator 精讲(通俗理解 + 用法)
C# IEnumerable 与 IEnumerator 精讲(通俗理解+用法)
一、核心定义(一句话看懂)
IEnumerator:迭代器,负责逐个遍历集合元素(存当前位置、取当前元素、移动到下一个)。
IEnumerable:可枚举接口,只负责对外提供一个迭代器。
两者关系:IEnumerable 依赖 IEnumerator,所有能 foreach 遍历的对象,都必须实现 IEnumerable。
二、两个接口源码拆解
1. IEnumerator(迭代器,遍历的“工具”)
位于 System.Collections,包含3个成员:
public interface IEnumerator
{
// 当前指向的元素
object Current { get; }
// 移动到下一个元素,成功返回true,无元素返回false
bool MoveNext();
// 重置指针到起始位置之前
void Reset();
}
作用:手动控制遍历流程:指针移动 → 取值。
2. IEnumerable(可枚举,“能被遍历”的标识)
public interface IEnumerable
{
// 唯一方法:返回一个迭代器 IEnumerator
IEnumerator GetEnumerator();
}
作用:告诉编译器:我这个类可以被 foreach 遍历。
三、二者核心关系(重点)
- IEnumerable 是对外入口:外界(foreach)调用
GetEnumerator()拿到迭代器。 - IEnumerator 是实际执行者:真正做遍历、取元素。
- 层级:foreach → IEnumerable → IEnumerator
类比理解(超形象)
- 集合(List/Array):一本书
- IEnumerable:书本自带“翻页工具入口”
- IEnumerator:书签(记录读到第几页、翻下一页、回看)
Current:当前页码内容MoveNext():翻下一页Reset():书签回到第一页前
四、foreach 底层原理(必懂)
foreach 本质就是语法糖,编译器会自动翻译成 IEnumerator 手动遍历代码。
示例:普通 foreach
List<int> list = new List<int> { 1, 2, 3 };
foreach (var item in list)
{
Console.WriteLine(item);
}
编译器翻译成的原生代码(等价)
IEnumerator enumerator = list.GetEnumerator(); // 拿到迭代器
while (enumerator.MoveNext()) // 不断下移指针
{
int item = (int)enumerator.Current; // 取当前元素
Console.WriteLine(item);
}
结论:只要实现 IEnumerable,就能用 foreach。
五、手写实现(自定义可遍历类,加深理解)
下面自己写一个集合,手动实现 IEnumerable + IEnumerator,模拟 List 遍历。
完整可运行代码
using System;
using System.Collections;
// 自定义集合:实现 IEnumerable
public class MyCollection : IEnumerable
{
private readonly object[] _items;
public MyCollection(object[] items)
{
_items = items;
}
// 实现接口:返回自定义迭代器
public IEnumerator GetEnumerator()
{
return new MyEnumerator(_items);
}
}
// 自定义迭代器:实现 IEnumerator
public class MyEnumerator : IEnumerator
{
private readonly object[] _items;
private int _index = -1; // 指针初始在第一个元素之前
public MyEnumerator(object[] items)
{
_items = items;
}
// 当前元素
public object Current => _items[_index];
// 移动指针
public bool MoveNext()
{
_index++;
// 下标没越界 = 还有元素
return _index < _items.Length;
}
// 重置指针
public void Reset()
{
_index = -1;
}
}
// 测试
class Program
{
static void Main()
{
var coll = new MyCollection(new object[] { "A", "B", "C" });
// 1. 直接 foreach(正常使用)
foreach (var item in coll)
{
Console.WriteLine(item);
}
Console.WriteLine("-----手动迭代-----");
// 2. 手动使用 IEnumerator(底层写法)
IEnumerator en = coll.GetEnumerator();
while (en.MoveNext())
{
Console.WriteLine(en.Current);
}
}
}
运行结果:
A
B
C
-----手动迭代-----
A
B
C
六、泛型版本(日常开发最常用)
上面是非泛型(返回 object,需要装箱拆箱),C# 实际推荐用泛型接口:
IEnumerable<T>IEnumerator<T>
泛型接口优势
- 类型安全,无需强制转换
- 无装箱拆箱,性能更高
泛型简化示例
using System;
using System.Collections.Generic;
public class MyGenericCollection<T> : IEnumerable<T>
{
private readonly T[] _data;
public MyGenericCollection(T[] data) => _data = data;
public IEnumerator<T> GetEnumerator()
{
foreach (var item in _data)
yield return item; // 后面讲的迭代器语法糖
}
// 显式实现非泛型接口(兼容老代码)
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
// 测试
class Program
{
static void Main()
{
var nums = new MyGenericCollection<int>(new[] { 10, 20, 30 });
foreach (var n in nums)
{
Console.WriteLine(n);
}
}
}
七、yield return(迭代器语法糖,重点实用)
手写完整 IEnumerator 很繁琐,C# 提供 yield 关键字,自动帮你生成迭代器。
规则
- 在
GetEnumerator()里用yield return 元素 - 编译器自动生成
IEnumerator类,不用手动写
最简示例
using System;
using System.Collections.Generic;
public class Demo
{
// 返回 IEnumerable<int>
public static IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
static void Main()
{
foreach (var num in GetNumbers())
{
Console.WriteLine(num);
}
}
}
执行特点:
yield 是延迟执行:遍历到哪,执行到哪,不会一次性加载所有元素,适合大数据/流式遍历。
八、总结(速记)
- IEnumerator:迭代器,负责移动指针、取元素(遍历执行者)。
- IEnumerable:可枚举接口,只暴露
GetEnumerator(),提供迭代器(遍历入口)。 - foreach = 语法糖,底层调用
GetEnumerator()+MoveNext()+Current。 - 开发优先用 泛型
IEnumerable<T>/IEnumerator<T>,类型安全、性能好。 - 自定义遍历优先用
yield return,简化代码。
一句话关系
IEnumerable 生产 IEnumerator,foreach 消费 IEnumerator。
下面分基础手动实现、泛型版本、手动遍历、yield 简化、场景实战多组示例,覆盖日常用法,代码均可直接运行。
一、非泛型版本(IEnumerable + IEnumerator 纯手动实现)
最原始写法,理解底层指针逻辑。
using System;
using System.Collections;
// 自定义集合:实现 IEnumerable
public class CustomCollection : IEnumerable
{
private readonly string[] _items;
public CustomCollection(string[] items)
{
_items = items;
}
// 对外提供迭代器
public IEnumerator GetEnumerator()
{
return new CustomEnumerator(_items);
}
}
// 自定义迭代器:实现 IEnumerator
public class CustomEnumerator : IEnumerator
{
private readonly string[] _data;
private int _index = -1; // 初始指针在第一个元素前
public CustomEnumerator(string[] data)
{
_data = data;
}
// 当前元素
public object Current => _data[_index];
// 移动指针
public bool MoveNext()
{
_index++;
return _index < _data.Length;
}
// 重置指针
public void Reset()
{
_index = -1;
}
}
class Program
{
static void Main()
{
var collection = new CustomCollection(new[] { "苹果", "香蕉", "橙子" });
// 1. foreach 遍历(常用写法)
Console.WriteLine("=== foreach 遍历 ===");
foreach (var item in collection)
{
Console.WriteLine(item);
}
// 2. 手动使用 IEnumerator 遍历(底层原生写法)
Console.WriteLine("\n=== 手动 IEnumerator 遍历 ===");
IEnumerator enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
// 3. Reset 重置后再次遍历
enumerator.Reset();
Console.WriteLine("\n=== 重置后再次遍历 ===");
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
}
}
二、泛型版本(IEnumerable + IEnumerator)
项目主流用法,类型安全、无装箱拆箱。
using System;
using System.Collections;
using System.Collections.Generic;
// 泛型自定义集合
public class GenericCollection<T> : IEnumerable<T>
{
private readonly T[] _arr;
public GenericCollection(T[] arr)
{
_arr = arr;
}
// 泛型迭代器
public IEnumerator<T> GetEnumerator()
{
return new GenericEnumerator<T>(_arr);
}
// 显式实现非泛型接口(兼容旧框架)
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
// 泛型迭代器
public class GenericEnumerator<T> : IEnumerator<T>
{
private readonly T[] _source;
private int _pos = -1;
public GenericEnumerator(T[] source)
{
_source = source;
}
public T Current => _source[_pos];
// 非泛型 Current,显式实现
object IEnumerator.Current => Current;
public bool MoveNext()
{
_pos++;
return _pos < _source.Length;
}
public void Reset()
{
_pos = -1;
}
// 泛型迭代器实现 IDisposable,释放资源
public void Dispose()
{
// 此处无资源可释放,实际场景可清理句柄、连接等
}
}
class Program
{
static void Main()
{
var numColl = new GenericCollection<int>(new[] { 1, 2, 3, 4, 5 });
Console.WriteLine("=== 泛型 foreach ===");
foreach (int num in numColl)
{
Console.Write(num + " ");
}
Console.WriteLine("\n\n=== 泛型 IEnumerator 手动遍历 ===");
using (IEnumerator<int> en = numColl.GetEnumerator())
{
while (en.MoveNext())
{
Console.Write(en.Current + " ");
}
}
}
}
三、yield return 简化迭代器(开发最常用)
不用手动写 IEnumerator 类,编译器自动生成,代码极简。
示例1:基础 yield 返回 IEnumerable
using System;
using System.Collections.Generic;
class Program
{
// 返回可枚举序列,延迟执行
public static IEnumerable<int> GetRange(int start, int count)
{
for (int i = 0; i < count; i++)
{
yield return start + i;
}
}
static void Main()
{
// foreach 遍历
foreach (var n in GetRange(10, 5))
{
Console.WriteLine(n);
}
// 手动获取迭代器遍历
Console.WriteLine("\n手动迭代器遍历:");
IEnumerator<int> en = GetRange(20, 3).GetEnumerator();
while (en.MoveNext())
{
Console.WriteLine(en.Current);
}
}
}
示例2:带条件中断(yield break)
using System;
using System.Collections.Generic;
class Program
{
public static IEnumerable<int> GetEvenNumbers(int max)
{
for (int i = 1; i <= max; i++)
{
if (i % 2 != 0)
continue;
if (i > 8)
yield break; // 终止迭代
yield return i;
}
}
static void Main()
{
foreach (var item in GetEvenNumbers(20))
{
Console.Write(item + " "); // 输出:2 4 6 8
}
}
}
示例3:提供多种迭代方式(如反向迭代、过滤迭代)
可以公开返回 IEnumerable
public class MyCollection<T> : IEnumerable<T>
{
// ... 存储部分同前
// 默认正序迭代(通过 GetEnumerator 提供)
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < _count; i++) yield return _items[i];
}
// 反向迭代器
public IEnumerable<T> Reverse()
{
for (int i = _count - 1; i >= 0; i--) yield return _items[i];
}
// 条件迭代(例如只返回大于某值的元素,假设 T 实现了 IComparable)
public IEnumerable<T> Where(Func<T, bool> predicate)
{
for (int i = 0; i < _count; i++)
if (predicate(_items[i]))
yield return _items[i];
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
使用示例:
foreach (var item in col.Reverse()) { ... }
foreach (var item in col.Where(x => x.Length > 1)) { ... }
四、结合集合过滤 / 链式使用(LINQ 风格)
IEnumerable 是 LINQ 基础,演示遍历+筛选:
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<string> names = new List<string> { "张三", "李四", "王五", "赵六" };
// 1. 拿到 IEnumerable<string>
IEnumerable<string> query = names.Where(s => s.Length == 2);
// 2. 手动迭代器遍历
IEnumerator<string> en = query.GetEnumerator();
Console.WriteLine("LINQ 筛选后手动遍历:");
while (en.MoveNext())
{
Console.WriteLine(en.Current);
}
// 3. 常规 foreach
Console.WriteLine("\nforeach 遍历:");
foreach (var name in query)
{
Console.WriteLine(name);
}
}
}
五、自定义类直接实现 IEnumerable(整合写法)
把集合和迭代逻辑写在同一个类中,项目常见封装形式:
using System;
using System.Collections;
using System.Collections.Generic;
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
// 学生列表集合,直接实现可枚举接口
public class StudentList : IEnumerable<Student>
{
private readonly List<Student> _students = new List<Student>();
public void Add(Student stu) => _students.Add(stu);
// 使用 yield 快速实现迭代器
public IEnumerator<Student> GetEnumerator()
{
foreach (var s in _students)
yield return s;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
class Program
{
static void Main()
{
var stuList = new StudentList
{
new Student { Name = "小明", Age = 18 },
new Student { Name = "小红", Age = 17 }
};
// foreach 遍历自定义集合
foreach (var stu in stuList)
{
Console.WriteLine($"姓名:{stu.Name},年龄:{stu.Age}");
}
}
}
关键知识点总结
IEnumerable:只有GetEnumerator(),作用是产出迭代器。IEnumerator:MoveNext()移动指针、Current取元素、Reset()复位。- 优先用泛型
IEnumerable<T>/IEnumerator<T>,性能&类型安全更好。 yield return是语法糖,自动生成迭代器,90% 业务场景推荐使用。foreach本质就是编译器翻译为IEnumerator的while(MoveNext())逻辑。

浙公网安备 33010602011771号