C#迭代器的详细用法

什么是迭代器

迭代器是一种设计模式,可以让开发人员无需关心容器对象的底层架构,就可以遍访这个容器对象。简单来说,迭代器就是用来遍历一个序列中的所有对象。

在C#中可以使用foreach关键字就可以枚举一个序列

foreach (var item in collection)
{
    Console.WriteLine(item?.ToString());
}

但foreach语句并非完美无缺,它依赖于.NET Core库中的两个接口:IEnumerableIEnumerator,接下来我们通过这两个接口实现foreach语句

IEnumerable是可枚举的意思,IEnumerator是枚举器的意思

IEnumerable接口

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

继承这个接口需要实现暴露出来的GetEnumerator方法,返回一个IEnumerator对象

IEnumerator接口

public interface IEnumerator
{
    object Current { get; } 
    bool MoveNext();
    void Reset();
}

IEnumerator接口有三个东西,Current返回当前序列的元素,方法MoveNext()移动到下一个元素,Reset方法重置,所以继承这个接口需要实现这三个东西

从这个两个接口对比就可以发现,对于枚举一个容器,起真正作用是IEnumerator

所以一个对象只要实现IEnumerator接口就能遍历

下面来看一个实例

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Csharp_study.Day1
{
    //遍历对象
    public class Anim
    {
        public string name;//动物的名称
        //构造方法,给name赋值
        public Anim(string name)
        {
            this.name = name;
        }
    }
    //枚举器
    public class MIEnumerator : IEnumerator
    {
        Anim[] anim;
        int idx = -1;
        //构造函数,anim赋值
        public MIEnumerator(Anim[] t)
        {
            anim = t;
        }
        //实现IEnumerator接口的Current方法,获取当前元素
        public object Current
        {
            get
            {
                if (idx == -1)
                    return new IndexOutOfRangeException();
                return anim[idx];
            }
        }
        //实现IEnumerator接口的MoveNext方法,向下一个元素移动
        public bool MoveNext()
        {
            idx++;
            return anim.Length > idx;
        }
        //实现IEnumerator接口的Reset方法,重置迭代器状态
        public void Reset()
        {
            idx = -1;
        }
    }
    class Class1
    {
        static void Main(string[] args)
        {
            //初始化一个Anim序列,用来遍历
            Anim[] anims = new Anim[] { new Anim("老虎"), new Anim("大象"), new Anim("河马") };
            MIEnumerator enumerator = new MIEnumerator(anims);
            while (enumerator.MoveNext())
            {
                Anim test = enumerator.Current as Anim;
                Console.WriteLine(test.name);
            } 
            Console.ReadLine();
        }

    }
}

输出结果

老虎
大象
河马

这段代码就不细说了,看注释就能明白。从这个例子中就可以看出来,我们通过继承这个IEnumerator接口,然后实现它的Current,MoveNext和Reset方法就可以遍历这个Anim对象了。

所以不难看出,foreach关键字就是主要依靠IEnumerator接口实现。那IEnumerable接口是干嘛的呢?上面已经说了,它只有一个GetEnumerator方法,并且返回的是一个IEnumerator类型的对象,所以说IEnumerable的作用就是获得可用于循环访问集合的IEnumerator对象

我们最终是要通过这两个接口来实现foreach,所以我们对上面的代码进行一下改动

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Csharp_study.Day1
{
    //IEnumerable接口
    public class Anim : IEnumerable
    {
        public string name;//动物名称
        Anim[] t;//anim对象数组
        //构造方法重载1-name赋值
        public Anim(string name)
        {
            this.name = name;
        }
        //构造方法重载2-t赋值
        public Anim(Anim[] p)
        {
            t = p;
        }
        //实现IEnumerable接口的GetEnumerator方法
        public IEnumerator GetEnumerator()
        {
            Console.WriteLine("调用GetEnumerator方法!");
            return new MIEnumerator(t);
        }
    }
    //实现IEnumerator接口
    public class MIEnumerator : IEnumerator
    {
        Anim[] anim;
        int idx = -1;
        public MIEnumerator(Anim[] t)
        {
            anim = t;
        }
        //实现IEnumerator接口的Current方法,获取当前元素
        public object Current
        {
            get
            {
                Console.WriteLine("调用Current方法!");
                if (idx == -1)
                    return new IndexOutOfRangeException();
                return anim[idx];
            }
        }
        //实现IEnumerator接口的MoveNext方法,向下一个元素移动
        public bool MoveNext()
        {
            Console.WriteLine("调用MoveNext方法!");
            idx++;
            return anim.Length > idx;
        }
        //实现IEnumerator接口的Reset方法,重置迭代器状态
        public void Reset()
        {
            Console.WriteLine("调用Reset方法!");
            idx = -1;
        }
    }
    class Class1
    {
        static void Main(string[] args)
        {
            //初始化一个Anim序列,用来遍历
            Anim[] anims = new Anim[] { new Anim("老虎"), new Anim("大象"), new Anim("河马") };
            Anim anim_t = new Anim(anims);
            //foreach遍历
            foreach (Anim item in anim_t)
            {
                Console.WriteLine(item.name);
            }
            Console.ReadLine();
        }

    }
}

输出结果

调用GetEnumerator方法!
调用MoveNext方法!
调用Current方法!
老虎
调用MoveNext方法!
调用Current方法!
大象
调用MoveNext方法!
调用Current方法!
河马
调用MoveNext方法!

通过输出结果可以发现,foreach在运行时会先调用Anim类中的GetEnumerator方法获得一个MIEnumerator,然后通过MIEnumerator的MoveNext方法后移,在调用Current方法获得元素的值,之后一直循环MoveNext和Current方法来遍历整个Anim序列

不难得出,GetEnumerator方法负责获取枚举器IEnumerator,MoveNext方法负责向下一个元素移动,Current方法负责返回当前的元素的值,Reset方法负责重置枚举器状态

总结

IEnumerable接口的作用就是获得枚举器IEnumerator,而IEnumerator接口的作用才是迭代的主体,负责遍历序列

通过这两段代码就可以看出foreach遍历的原理是先通过IEnumerable获得枚举器IEnumerator,然后通过枚举器的MoveNext和Current方法来遍历序列。

yield关键字

此外在迭代器中还有一个关键字需要我们掌握-yield。yield是迭代器中的关键字,用于向枚举对象提供值或发出迭代结束的信号

yield是一个语法糖,是为了简化迭代器的实现语法才产生的,从上面的讲解不难发现,在枚举器中起实际起作用的就是MoveNext和Current方法。所以C#2提供一个处理方法:yield语句。

有了yield语句,我们可以在自己的类或者序列中支持foreach迭代而不必实现IEnumerator和IEnumerable接口,我们只要提供一个迭代器,当编辑器检测到迭代器时,会自动生成IEnumerator接口中的Current,MoveNext和Reset方法。

通过yield return xxx 可以向枚举对象提供值,通过yield break来中止迭代

下面可以看一个例子

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Csharp_study.Day1
{
    class Class1
    {
        static void Main(string[] args)
        {
            foreach(int item in GetSingleDigitNumbers())
            {
                Console.WriteLine(item);
            }
            IEnumerable<int> GetSingleDigitNumbers()
            {
                yield return 0;
                yield return 1;
                yield return 2;
                yield return 3;
                yield return 4;
                yield return 5;
                yield return 6;
                yield return 7;
                yield return 8;
                yield return 9;
            }
            Console.ReadKey();
        }
    }
}

输出结果

0
1
2
3
4
5
6
7
8
9

我们使用IEnumerable<int>来表示一个int序列,通过yield return 来向这个序列中添加元素,然后直接在foreach就可以遍历这个int序列了,完全不需要MoveNext方法和Current方法就可以实现遍历。通过yield系统会自动生成这两种方法。

注意,yield只能用于迭代器器,也就是说yield只能用于返回值是IEnumertor和IEnumerable接口及其泛型版本

如有错漏之处,敬请指正!

  • 11
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

真的没事鸭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值