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迭代器)
实现复杂度 简单(仅实现一个接口方法) 极简(无需实现接口,仅编写迭代器逻辑)
可读性 高(符合可遍历类规范,直观易懂) 中等(需理解迭代器方法的使用场景)
相关推荐
笨手笨脚の2 小时前
深入理解 Java 虚拟机-01 JVM 内存模型
java·jvm··虚拟机栈·方法区
csbysj20202 小时前
Perl 目录操作指南
开发语言
-To be number.wan2 小时前
C++ 运算符重载入门:让“+”也能为自定义类型服务!
开发语言·c++
未来之窗软件服务2 小时前
幽冥大陆(七十九)Python 水果识别训练视频识别 —东方仙盟练气期
开发语言·人工智能·python·水果识别·仙盟创梦ide·东方仙盟
王家视频教程图书馆2 小时前
android java 开发网路请求库那个好用请列一个排行榜
android·java·开发语言
花卷HJ2 小时前
Android 文件工具类 FileUtils(超全封装版)
android·java
rchmin2 小时前
ThreadLocal内存泄漏机制解析
java·jvm·内存泄露
报错小能手2 小时前
数据结构 b+树
数据结构·b树·算法
黎雁·泠崖2 小时前
Java 方法栈帧深度解析:从 JIT 汇编视角,打通 C 与 Java 底层逻辑
java·c语言·汇编