C#中的枚举器
阅读原文时间:2023年07月08日阅读:1

更新记录

本文迁移自Panda666原博客,原发布时间:2021年6月28日。

一、先从可枚举类型讲起

1.1 什么是可枚举类型?

可枚举类型,可以简单的理解为:

有一个类,类中有挺多的数据,用一种统一的方式把他们列举出来。

在.NET中满足以下任意条件的都是可枚举类型:

Implements System.Collections.IEnumerable

Implements System.Collections.Generic.IEnumerable

Has a public parameterless method named GetEnumerator that returns an enumerator

这三条看起来非常的简单。第一条实现了IEnumerable接口就行了。第二条实现了IEnumerable接口就行了。第三条就更简洁了,实现了GetEnumerator方法就行了。所以可以发现,可枚举类型实现起来非常的方便。

1.2 那么问题来了,为什么需要可枚举类型?

上面不是说了吗,用一种统一的方式把数据列举出来。

那么问题来了,为什么需要用统一的方式取出数据?

因为方便取出数据,一百个类有一百个取出数据的方式,维护起来就很难受了。

所以统一数据的取出方式,是非常有必要的。

1.3 具体实现可枚举类型

说了这么多,是什么,为什么,来看看怎么用。我们先来看看,上面说到的2个接口。

System.Collections.IEnumerable接口的定义

namespace System.Collections
{
    public interface IEnumerable
    {
        IEnumerator GetEnumerator();
    }
}

System.Collections.Generic.IEnumerable接口 的定义

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

注意点:

可以看到2个接口大同小异,都有GetEnumerator方法,但要注意的是2个方法的返回类型是不同的。

GetEnumerator从字面意思也能看出来,表示获得Enumerator。

那么什么是Enumerator?Enumerator表示枚举器。

我们的数据统一数据的取出方式全部都靠枚举器帮我们代劳,而不是直接定义在本类中,当然你也可以这样做,但不建议。接下来现在我们来定义几个可枚举类型。

继承自IEnumerable的可枚举类型

public class PandaClass1 : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        //内部没有具体实现,所以先抛出异常
        throw new NotImplementedException();
    }
}

继承自IEnumerable的可枚举类型

public class PandaClass2 : IEnumerable<int>
{
    public IEnumerator<int> GetEnumerator()
    {
        //内部没有具体实现,所以先抛出异常
        throw new NotImplementedException();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        //内部没有具体实现,所以先抛出异常
        throw new NotImplementedException();
    }
}

直接实现GetEnumerator方法的可枚举类型

public class PandaClass3
{
    public IEnumerator GetEnumerator()
    {
        //内部没有具体实现,所以先抛出异常
        throw new NotImplementedException();
    }
}

直接实现GetEnumerator方法的可枚举类型

public class PandaClass4
{
    public IEnumerator<int> GetEnumerator()
    {
        //内部没有具体实现,所以先抛出异常
        throw new NotImplementedException();
    }
}

到这里我们已经搞定可枚举类型了,怎么进行枚举?

二、有趣的枚举器(Enumerator)

2.1 那么问题来了,什么是枚举器。

Enumerator在英文中有列举出人或物的意思。所以是列举器的意思?不急,我们来对比看看程序中什么是枚举器。

在.NET中满足以下任意条件的都是枚举器:

1、Implements System.Collections.IEnumerator

2、Implements System.Collections.Generic.IEnumerator

3、Has a public parameterless method named MoveNext and property called Current

第三条中的实现MoveNext方法和Current属性倒是挺好理解的,只要实现了这2个成员就叫枚举器。

比如下面这类就叫枚举器

public class Enumerator
{
  public IteratorVariableType Current { get {...} }
  public bool MoveNext() {...}
}

那么第一二条中的接口是什么,不慌,我们先来看看这2个接口的定义。

IEnumerator接口的定义

namespace System.Collections
{
    public interface IEnumerator
    {
        object Current { get; }
        bool MoveNext();
        void Reset();
    }
}

IEnumerator接口的定义

namespace System.Collections.Generic
{
    public interface IEnumerator<out T> : IEnumerator, IDisposable
    {
        T Current { get; }
    }
}

需要注意的点:

IEnumerator接口返回的是object类型。

从接口的定义我们可以发现IEnumerator继承自IEnumerator。

所以IEnumerator是同样包含的有MoveNext()和Reset()方法的。

MoveNext()方法表示移动到下一个元素。

Reset()方法表示重置枚举器。

Current属性用于获得当前的元素值。

2.2 实现枚举器

直接继承IEnumerator接口实现枚举器

public class PandaEnumerator1 : IEnumerator
{
    //内部没有具体实现,所以先抛出异常
    public object Current => throw new NotImplementedException();

    public bool MoveNext()
    {
        //内部没有具体实现,所以先抛出异常
        throw new NotImplementedException();
    }

    public void Reset()
    {
        //内部没有具体实现,所以先抛出异常
        throw new NotImplementedException();
    }
}

直接继承IEnumerator接口实现枚举器

public class PandaEnumerator2 : IEnumerator<int>
{
    //内部没有具体实现,所以先抛出异常
    public int Current => throw new NotImplementedException();

    //内部没有具体实现,所以先抛出异常
    object IEnumerator.Current => throw new NotImplementedException();

    //用于释放枚举器使用的资源
    public void Dispose()
    {
        throw new NotImplementedException();
    }

    public bool MoveNext()
    {
        //内部没有具体实现,所以先抛出异常
        throw new NotImplementedException();
    }

    public void Reset()
    {
        //内部没有具体实现,所以先抛出异常
        throw new NotImplementedException();
    }
}

直接实现要求的方法实现枚举器

public class PandaEnumerator3
{
    //内部没有具体实现,所以先抛出异常
    public object Current => throw new NotImplementedException();

    public bool MoveNext()
    {
        //内部没有具体实现,所以先抛出异常
        throw new NotImplementedException();
    }

    public void Reset()
    {
        //内部没有具体实现,所以先抛出异常
        throw new NotImplementedException();
    }
}

三、组合枚举类型和枚举器

那么问题来了,枚举类型和枚举器都搞定了。怎么把他们组合起来?怎么用来?

我们这里用一个实例来进行演示。我们先定义一个学生类来表示要枚举的数据。

/// <summary>
/// 学生数据
/// </summary>
public class Student
{
    /// <summary>
    /// 编号
    /// </summary>
    public string Id { get; set; }

    /// <summary>
    /// 姓名
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 年龄
    /// </summary>
    public int Age { get; set; }
}

然后我们定义枚举器

/// <summary>
/// 枚举器
/// </summary>
public class StudentIEnumerator : IEnumerator<Student>
{
    /// <summary>
    /// 用于遍历的学生信息
    /// </summary>
    private List<Student> InnerStudents { get; set; }

    /// <summary>
    /// 当前的位置
    /// </summary>
    private int CurrentPosition { get; set; }

    public StudentIEnumerator(List<Student> students)
    {
        this.InnerStudents = students;
        this.CurrentPosition = -1;
    }

    //获得当前的元素
    public Student Current => this.InnerStudents[this.CurrentPosition];

    //如果无需兼容老代码,可以无需修改
    object IEnumerator.Current => throw new NotImplementedException();

    public void Dispose()
    {

    }

    public bool MoveNext()
    {
        if(this.CurrentPosition < this.InnerStudents.Count-1)
        {
            ++this.CurrentPosition;
            return true;
        }

        return false;
    }

    public void Reset()
    {
        this.CurrentPosition = -1;
    }
}

最后定义可枚举类型

/// <summary>
/// 可枚举类型
/// </summary>
public class StudentCollecion : IEnumerable<Student>
{
    /// <summary>
    /// 学生数据
    /// </summary>
    public List<Student> Students { get; set; }

    public StudentCollecion()
    {
        //初始化List
        this.Students = new List<Student>();
        //虚构用于测试的学生信息
        this.Students.Add(new Student() {
            Id = "666",
            Name = "Panda",
            Age = 18
        });

        this.Students.Add(new Student()
        {
            Id = "888",
            Name = "Donkey",
            Age = 18
        });

        this.Students.Add(new Student()
        {
            Id = "999",
            Name = "Dog",
            Age = 18
        });

        this.Students.Add(new Student()
        {
            Id = "777",
            Name = "Pig",
            Age = 18
        });

        this.Students.Add(new Student()
        {
            Id = "333",
            Name = "Monkey",
            Age = 18
        });

    }

    /// <summary>
    /// 获得枚举器
    /// 返回我们自定义的配套枚举器
    /// </summary>
    /// <returns></returns>
    public IEnumerator<Student> GetEnumerator()
    {
        return new StudentIEnumerator(this.Students);
    }

    //如果无需兼容老代码可以不用修改
    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

四、使用可枚举类型

那么问题来了,怎么使用可枚举类型?

方式一:使用foreach。这是最常用的方式。

StudentCollecion students = new StudentCollecion();

foreach (var student in students)
{
    Console.WriteLine(student.Id);
}

注意:foreach只是方式二的语法糖。如果枚举器实现了IDisposable接口,foreach会自动调用dispose方法。

方式二:直接使用迭代器

StudentCollecion students = new StudentCollecion();
//获得实例的枚举器
IEnumerator<Student> enumerator = students.GetEnumerator();

//手动操作
while (enumerator.MoveNext())
{
    //获得元素值
    Console.WriteLine(enumerator.Current.Name);
}

//重置一下枚举器
enumerator.Reset();

五、总结

实例完整源代码

using System;
using System.Collections;
using System.Collections.Generic;
namespace Panda666comTest
{
    /// <summary>
    /// 枚举器
    /// </summary>
    public class StudentIEnumerator : IEnumerator<Student>
    {
        /// <summary>
        /// 用于遍历的学生信息
        /// </summary>
        private List<Student> InnerStudents { get; set; }

        /// <summary>
        /// 当前的位置
        /// </summary>
        private int CurrentPosition { get; set; }

        public StudentIEnumerator(List<Student> students)
        {
            this.InnerStudents = students;
            this.CurrentPosition = -1;
        }

        //获得当前的元素
        public Student Current => this.InnerStudents[this.CurrentPosition];

        //如果无需兼容老代码,可以无需修改
        object IEnumerator.Current => throw new NotImplementedException();

        public void Dispose()
        {

        }

        public bool MoveNext()
        {
            if (this.CurrentPosition < this.InnerStudents.Count - 1)
            {
                ++this.CurrentPosition;
                return true;
            }
            return false;
        }

        public void Reset()
        {
            this.CurrentPosition = -1;
        }
    }

    /// <summary>
    /// 学生数据
    /// </summary>
    public class Student
    {
        /// <summary>
        /// 编号
        /// </summary>
        public string Id { get; set; }

        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 年龄
        /// </summary>
        public int Age { get; set; }
    }

    /// <summary>
    /// 可枚举类型
    /// </summary>
    public class StudentCollecion : IEnumerable<Student>
    {
        /// <summary>
        /// 学生数据
        /// </summary>
        public List<Student> Students { get; set; }
        public StudentCollecion()
        {
            //初始化List
            this.Students = new List<Student>();

            //虚构用于测试的学生信息
            this.Students.Add(new Student()
            {
                Id = "666",
                Name = "Panda",
                Age = 18
            });

            this.Students.Add(new Student()
            {
                Id = "888",
                Name = "Donkey",
                Age = 18
            });

            this.Students.Add(new Student()
            {
                Id = "999",
                Name = "Dog",
                Age = 18
            });

            this.Students.Add(new Student()
            {
                Id = "777",
                Name = "Pig",
                Age = 18
            });

            this.Students.Add(new Student()
            {
                Id = "333",
                Name = "Monkey",
                Age = 18
            });
        }

        /// <summary>
        /// 获得枚举器
        /// 返回我们自定义的配套枚举器
        /// </summary>
        /// <returns></returns>
        public IEnumerator<Student> GetEnumerator()
        {
            return new StudentIEnumerator(this.Students);
        }

        //如果无需兼容老代码可以不用修改
        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //实例化可枚举类型
            StudentCollecion students = new StudentCollecion();

            //方式一:使用foreach来遍历可枚举类型
            foreach (var student in students)
            {
                Console.WriteLine(student.Id);
            }

            //方式二:使用手动调用枚举器
            //获得实例的枚举器
            IEnumerator<Student> enumerator = students.GetEnumerator();

            //手动操作
            while (enumerator.MoveNext())
            {
                //获得元素值
                Console.WriteLine(enumerator.Current.Name);
            }

            //重置一下枚举器
            enumerator.Reset();
            enumerator.Dispose();

            //wait
            Console.WriteLine("Success");
            Console.ReadKey();
        }
    }
}