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 遍历


三、二者核心关系(重点)

  1. IEnumerable 是对外入口:外界(foreach)调用 GetEnumerator() 拿到迭代器。
  2. IEnumerator 是实际执行者:真正做遍历、取元素。
  3. 层级: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>

泛型接口优势

  1. 类型安全,无需强制转换
  2. 无装箱拆箱,性能更高

泛型简化示例

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延迟执行:遍历到哪,执行到哪,不会一次性加载所有元素,适合大数据/流式遍历。


八、总结(速记)

  1. IEnumerator:迭代器,负责移动指针、取元素(遍历执行者)。
  2. IEnumerable:可枚举接口,只暴露 GetEnumerator()提供迭代器(遍历入口)。
  3. foreach = 语法糖,底层调用 GetEnumerator() + MoveNext() + Current
  4. 开发优先用 泛型 IEnumerable<T>/IEnumerator<T>,类型安全、性能好。
  5. 自定义遍历优先用 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 的方法,内部使用 yield return:

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}");
        }
    }
}

关键知识点总结

  1. IEnumerable:只有 GetEnumerator(),作用是产出迭代器
  2. IEnumeratorMoveNext() 移动指针、Current 取元素、Reset() 复位。
  3. 优先用泛型 IEnumerable<T>/IEnumerator<T>,性能&类型安全更好。
  4. yield return 是语法糖,自动生成迭代器,90% 业务场景推荐使用。
  5. foreach 本质就是编译器翻译为 IEnumeratorwhile(MoveNext()) 逻辑。
posted @ 2026-06-10 05:48  人生就是修炼  阅读(101)  评论(0)    收藏  举报