IEnumerable、IEnumerator接口与yield return关键字的相关知识

IEnumerable :这是一个标记性接口 ,作用是告诉 C# 编译器:「这个对象是可以被foreach循环遍历的」,IEnumerable接口是非常的简单,只包含一个抽象的方法GetEnumerator(),它返回一个可用于循环访问集合的IEnumerator对象。

IEnumerator :这是一个功能接口,是实际执行遍历操作的 "工具",负责一步步获取集合中的元素。IEnumerator对象有什么呢?它是一个真正的集合访问器,没有它,就不能使用foreach语句遍历集合或数组,因为只有IEnumerator对象才能访问集合中的项,假如连集合中的项都访问不了,那么进行集合的循环遍历是不可能的事情了。

IEnumerator包含一个属性两个方法

MoveNext:把当前的项移动到下一项(类似于索引值),返回一个bool值,这个bool值用来检查当前项是否超出了枚举数的范围!

Current:获取当前项的值,返回一个object的类型!

Reset:顾名思义也就是把一些值恢复为默认值,比如把当前项恢复到默认状态值!

IEnumerable和IEnumerator区别

两者的职责完全不同

IEnumerable:「标记接口」,表示 "这个对象可以被遍历",核心方法 GetEnumerator() 用于提供枚举器

IEnumerator:「功能接口」,表示 "这个对象是遍历工具",核心方法 MoveNext()/ 属性 Current 用于实际执行遍历

通俗理解:IEnumerable 是 "水果篮"(可被遍历),IEnumerator 是 "取水果的夹子"(执行遍历),你不能直接遍历 "夹子",只能遍历 "水果篮"。

通过以下实例了解两者的区别:

一般写法(依赖内部集合的GetEnumerator()):

cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;

// 1. 自定义实体类(作为泛型类型参数)
public class Student
{
    // 学生属性
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return $"学生ID:{Id},姓名:{Name},年龄:{Age}";
    }
}

// 2. 自定义集合类,实现IEnumerable<T>(泛型接口),表示对象可以被遍历
public class StudentCollection : IEnumerable<Student>
{

    private List<Student> _studentList = new List<Student>();

    public StudentCollection()
    {
        // 添加测试数据
        _studentList.Add(new Student { Id = 1, Name = "张三", Age = 18 });
        _studentList.Add(new Student { Id = 2, Name = "李四", Age = 19 });
        _studentList.Add(new Student { Id = 3, Name = "王五", Age = 20 });
    }

    // 3. 实现IEnumerable<Student>的核心方法:GetEnumerator()
    public IEnumerator<Student> GetEnumerator()
    {
        // 直接返回内部List的泛型枚举器(简化实现,无需手动编写枚举器),获得遍历工具
        return _studentList.GetEnumerator();
    }

    // 4. 显式实现非泛型IEnumerable的GetEnumerator()方法(接口继承要求)
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }


}

// 测试类
class Program
{
    static void Main(string[] args)
    {
        StudentCollection studentCol = new StudentCollection();
        foreach (Student stu in studentCol)
        {
            Console.WriteLine(stu);
        }
    
        Console.ReadKey();
    }
}

装逼写法(手动实现GetEnumerator()的核心逻辑):

cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;

// 学生实体类
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return $"学生ID:{Id},姓名:{Name},年龄:{Age}";
    }
}

// 1. 自定义学生集合(实现IEnumerable<Student>),表示对象可以被遍历
public class StudentCollection : IEnumerable<Student>
{
    // 内部数据源(仅存储数据,不依赖其枚举器)
    private Student[] _studentArray;

    // 构造函数:初始化学生数据
    public StudentCollection()
    {
        _studentArray = new Student[]
        {
            new Student { Id = 1, Name = "张三", Age = 18 },
            new Student { Id = 2, Name = "李四", Age = 19 },
            new Student { Id = 3, Name = "王五", Age = 20 }
        };
    }

    // 2. 实现IEnumerable<Student>的GetEnumerator(),返回自定义枚举器
    public IEnumerator<Student> GetEnumerator()
    {
        // 不返回内部数组的枚举器,而是返回我们自定义的枚举器实例,获得遍历工具
        return new StudentEnumerator(_studentArray);
    }

    // 显式实现非泛型GetEnumerator(兼容要求)
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    // 3. 自定义枚举器类(定义遍历工具 手动维护遍历逻辑)
    private class StudentEnumerator : IEnumerator<Student>
    {
        // 引用外部集合的数据源
        private Student[] _students;
        // 遍历状态:当前索引(初始值-1,表示未开始遍历)
        private int _currentIndex = -1;

        // 构造函数:接收数据源
        public StudentEnumerator(Student[] students)
        {
            _students = students;
        }

        //泛型Current属性(显式实现,接口)
        public Student Current
        {
            get
            {
                if (_currentIndex < 0 || _currentIndex >= _students.Length)
                {
                    throw new InvalidOperationException("当前无有效元素");
                }
                return _students[_currentIndex];
            }
        }

        // 非泛型Current属性(显式实现,接口)
        object IEnumerator.Current => Current;

        //实现MoveNext():更新遍历状态,判断是否有下一个元素,接口
        public bool MoveNext()
        {
            // 索引自增,判断是否超出数组长度
            if (_currentIndex < _students.Length - 1)
            {
                _currentIndex++;
                return true; // 有下一个元素
            }
            return false; // 遍历完毕
        }

       // 实现Reset():重置遍历状态(可选,多数场景不使用),接口
        public void Reset()
        {
            _currentIndex = -1;
        }

        //实现Dispose():释放资源
        public void Dispose()
        {
            // 若有非托管资源,在此处释放
        }
    }
}

// 测试类
class Program
{
    static void Main(string[] args)
    {
        StudentCollection studentCol = new StudentCollection();

        // foreach遍历(底层调用自定义的枚举器)
        foreach (Student stu in studentCol)
        {
            Console.WriteLine(stu);
        }
    }
}

还有在GetEnumerator()中使用yield return(简化自定义):

cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;

// 学生实体类
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return $"学生ID:{Id},姓名:{Name},年龄:{Age}";
    }
}

// 自定义学生集合(实现IEnumerable<Student>)
public class StudentCollection : IEnumerable<Student>
{
    private Student[] _studentArray;

    public StudentCollection()
    {
        _studentArray = new Student[]
        {
            new Student { Id = 1, Name = "张三", Age = 18 },
            new Student { Id = 2, Name = "李四", Age = 19 },
            new Student { Id = 3, Name = "王五", Age = 20 }
        };
    }

    // 手动实现GetEnumerator()逻辑,不返回内置集合的枚举器
    public IEnumerator<Student> GetEnumerator()
    {
        // 正序遍历(自定义索引控制,无依赖内置枚举器)
        for (int i = 0; i < _studentArray.Length; i++)
        {
            yield return _studentArray[i]; // 逐个返回元素,编译器自动生成状态机
        }
   
    }

    // 显式实现非泛型GetEnumerator,接口要求
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

// 测试类
class Program
{
    static void Main(string[] args)
    {
        StudentCollection studentCol = new StudentCollection();
        foreach (Student stu in studentCol)
        {
            Console.WriteLine(stu);
        }
    }
}

实现foreach遍历

1.继承IEnumerable接口,支持foreach遍历

MyInt类的核心是规范实现IEnumerable接口,将遍历职责委托给内部数组的内置枚举器 ,无需手动编写枚举器的核心逻辑(MoveNext()、Current)。

cs 复制代码
    public class MyInt : IEnumerable
    {
        // 内部存储的数据源:整型数组
        int[] temp = { 1, 32, 43, 343 };

        // 实现IEnumerable接口的唯一抽象方法:GetEnumerator()
        public IEnumerator GetEnumerator()
        {
            // 直接返回内部数组的枚举器
            return temp.GetEnumerator();
        }
    }

    public class TestClass
    {
        public static void Test_MyInt()
        {
            MyInt temp = new MyInt();
            foreach (int item in temp)
                Console.WriteLine(item);
        }
      
    }

2.通过通过返回IEnumerable<int>类型的迭代器方法间接支持foreach遍历

MyInt2类未实现任何可遍历接口,而是通过返回IEnumerable<int>类型的迭代器方法,间接支持foreach遍历,核心依赖yield return关键字的惰性执行特性。

cs 复制代码
    public class MyInt2
    {
        int[] temp = { 1, 32, 43, 343 };

       public IEnumerable<int> GetEnumer()
        {
            int index = -1; 
            while (index < temp.Length - 1) 
            {
                index++;
                yield return temp[index];
            }
        }
    }

    public class TestClass
    {

        public static void Test_MyInt2()
        {
            MyInt2 temp = new MyInt2();
            var IEnumeratorValue= temp.GetEnumer(); 
            foreach (int item in IEnumeratorValue)
                Console.WriteLine(item);
        }

    }
对比维度 MyInt MyInt2
可遍历接口实现 实现非泛型IEnumerable接口 未实现任何可遍历接口
底层实现原理 委托数组内置枚举器(无yield关键字) 编译器自动生成状态机(yield return迭代器)
实现复杂度 简单(仅实现一个接口方法) 极简(无需实现接口,仅编写迭代器逻辑)
可读性 高(符合可遍历类规范,直观易懂) 中等(需理解迭代器方法的使用场景)
相关推荐
SimonKing4 小时前
OpenCode AI辅助编程,不一样的编程思路,不写一行代码
java·后端·程序员
FastBean4 小时前
Jackson View Extension Spring Boot Starter
java·后端
Seven975 小时前
剑指offer-79、最⻓不含重复字符的⼦字符串
java
皮皮林55114 小时前
Java性能调优黑科技!1行代码实现毫秒级耗时追踪,效率飙升300%!
java
冰_河15 小时前
QPS从300到3100:我靠一行代码让接口性能暴涨10倍,系统性能原地起飞!!
java·后端·性能优化
地平线开发者15 小时前
SparseDrive 模型导出与性能优化实战
算法·自动驾驶
董董灿是个攻城狮16 小时前
大模型连载2:初步认识 tokenizer 的过程
算法
地平线开发者16 小时前
地平线 VP 接口工程实践(一):hbVPRoiResize 接口功能、使用约束与典型问题总结
算法·自动驾驶
罗西的思考16 小时前
AI Agent框架探秘:拆解 OpenHands(10)--- Runtime
人工智能·算法·机器学习