C#中关于IEnumerator与yield


前言

在上一篇小记(c#中关于IEnumerator与IEnumerable接口)中我们了解了IEnumerable接口与IEnumerator接口实现迭代,但是实现过程还是比较复杂的,那有没有方便一些的方法来实现呢?接下来我们就一起深入研究一些yield关键字。


一、yield是什么?

yield 不是保留字;仅当紧靠在或关键字之前使用时, 如在return或break前它才具有特殊意义。 在其他上下文中, yield 可用作标识符。

1.yield的声明:

yield_statement
    : 'yield' 'return' expression ';'
    | 'yield' 'break' ';'
    ;

2.yield的无效用法:

    delegate IEnumerable<int> D();

    IEnumerator<int> GetEnumerator()
    {
        try
        {
            yield return 1;        // Ok
            yield break;           // Ok
        }
        finally
        {
            yield return 2;        // Error, yield in finally
            yield break;           // Error, yield in finally
        }

        try
        {
            yield return 3;        // Error, yield return in try...catch
            yield break;           // Ok
        }
        catch
        {
            yield return 4;        // Error, yield return in try...catch
            yield break;           // Ok
        }

        D d = delegate {
            yield return 5;        // Error, yield in an anonymous function
        };
    }

    int MyMethod()
    {
        yield return 1;            // Error, wrong return type for an iterator block
    }

详细相关说明请看微软的官方文档

二、举例说明

1.实现代码

这次我们写一个链表类来完全脱离数组与List。
类代码:

    public class LinkeListNode<T>                                       //链表节点类
    {
        public T mValue { get; private set; }                           //存放当前链表数据
        public LinkeListNode(T value)									//实例化节点
        {
            mValue = value;
        }                              

        public LinkeListNode<T> Next { get; set; }                      //下一个节点的引用
        public LinkeListNode<T> Prev { get; set; }                      //上一个节点的引用
    }
    public class LinkeList<T> : IEnumerable                             //链表类,派生自IEnumerable接口
    {
        public LinkeListNode<T> First { get; private set; }             //表头节点
        public LinkeListNode<T> Last { get; private set; }              //表尾节点

        public void LinkeAdd(T node)                                    //Add方法添加元素
        {
            LinkeListNode<T> mNode = new LinkeListNode<T>(node);
            if (First == null)
            {
                First = mNode;
                Last = First;
            }
            else
            {
                LinkeListNode<T> mLast = Last;
                mLast.Next = mNode;
                Last = mNode;
                Last.Prev = mLast;
            }
        }                                  
        public IEnumerator GetEnumerator()                              //获取迭代器(IEnumerator)
        {
            LinkeListNode<T> item = First;
            Console.WriteLine("首次进入GetIE");
            while (item != null)
            {
                Console.WriteLine("我在yield上方");
                yield return item.mValue;           //直接把item.mValue变成yield类型并分配给IEnumerator的Current属性
                                                    //返回值之后,再次进入迭代器的时候会继续在这里往下执行
                Console.WriteLine("我在yield上下方");//yield break;迭代结束后,返回的IEnumerator中的MoveNext就直接变为false了。
                item = item.Next;
            }
        }
    }

主程序代码:

   LinkeList<int> List = new LinkeList<int>();				//创建链表
   for (int i = 0; i < 12; i = i + 2)						//往链表中添加数据
   {
        List.LinkeAdd(i);
   }
   Console.WriteLine("准备进入foreach");
   foreach (var item in List)								//遍历打印
   {
        Console.WriteLine(item);
   }

2.运行结果

在这里插入图片描述
到此,对比上一章用IEnumerator接口实现,我们可以看见,通过运用yield return可以节省大量代码。那yield工作的机制是啥样的能?让我们继续往下讨论。

三、深入一些

1.yield return

来看第二节中的输出打印,我们可以大致的整理出代码的执行顺序:
此处,只放关键步骤代码

									//从主运行函数开始看
   LinkeList<int> List = new LinkeList<int>();		//创建链表
   for (int i = 0; i < 12; i = i + 2)				//往链表中添加数据
   {
        List.LinkeAdd(i);
   }
   Console.WriteLine("准备进入foreach");
   foreach (var item in List)						//首次进入foreach的时候会去调用GetEnumerator来得到IEnumerator(迭代器),此时跳至了下一段方法中

离开foreach进入GetEnumerator()方法

public IEnumerator GetEnumerator()					//获取迭代器(IEnumerator)        
{
	LinkeListNode<T> item = First;					//设定表头数据
	Console.WriteLine("首次进入GetIE");
	while (item != null)							//判断节点是否有数据
    {												//此时若节点直接无数据,直接就没有IEnumerator返回,则foreach直接结束。
		Console.WriteLine("我在yield上方");
		yield return item.mValue;          			//节点有数据进入,yield return自动封装了一个IEnumerator,
													//并把item.Value给了IEnumerator.Current,MoveNext()返回true。
													//并返回IEnumerator.Current(迭代器),并在方法内部当前位置挂起

离开GetEnumerator()方法进入foreach{}

        Console.WriteLine(item);					//打印返回的IEnumerator.current
   }
      foreach (var item in List)					//再次进入foreach跳转到GetEnumerator()中上一次挂起的位置继续进行

离开foreach进入GetEnumerator()方法

		Console.WriteLine("我在yield上下方");		//再次进入时,从上次挂起的地方往下执行
		item = item.Next;
	}
	while (item != null)							//判断节点是否有数据
    {												//此时若节点直接无数据,直接就没有IEnumerator返回,则foreach直接结束。
		Console.WriteLine("我在yield上方");
		yield return item.mValue;					//再次调到foreach{}中循环

2.yield break

结束迭代器迭代。
将GetEnumerator()方法中代码改成如下:

        public IEnumerator GetEnumerator()                              //获取迭代器(IEnumerator)
        {
            LinkeListNode<T> item = First;
            Console.WriteLine("首次进入GetIE");
            int i = 0;
            while (item != null)
            {
                i = i + 1;
                Console.WriteLine("我在yield上方");
                if (i>5)
                {
                    yield break;
                }
                yield return item.mValue;           //直接把item.mValue变成yield类型并分配给IEnumerator的Current属性
                                                    //返回值之后,再次进入迭代器的时候会继续在这里往下执行
                Console.WriteLine("我在yield下方"); //yield break;迭代结束后,返回的IEnumerator中的MoveNext就直接变为false了。
                item = item.Next;
            }
        }

会发现它打印了5次就跳出了(正常应该打印6次)。
所以,yield break是用来条件控制结束迭代的。
另外,yield break后会调用迭代器的Dispose()方法。


总结

本次通过代码进一步了解了迭代器与yield,任然还有很多未知,以后有时间会再研究一些yield的自实现,我们下次再见喽拜拜。

兼爱,非攻,天志,明鬼

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值