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的自实现,我们下次再见喽拜拜。
兼爱,非攻,天志,明鬼