理解yield return 的工作方式
yield return 是一个编译器暗示,告诉编译器要生成一个状态机。每当遇到一个含有yield return的代码时,则返回yield return后的表达式的值,并保存当前再代码中的位置。下次调用迭代器函数时,键从该位置重新开始执行。
static void Main(string[] args)
{
foreach (int i in GetEvents())
{
Debug.WriteLine("Main : " + i);
}
}
public static IEnumerable<int> GetEvents()
{
Debug.WriteLine("Init : ");
var integers = new[] { 1, 2, 3, 4};
foreach (int i in integers)
{
Debug.WriteLine("Loop Before : " + i);
if (i % 2 == 0)
{
Debug.WriteLine("Pause : " + i);
// 返回当前值,并保存当前状态,下一次调用从此开始
yield return i;
}
Debug.WriteLine("Loop After : " + i);
}
Debug.WriteLine("IDone : ");
}
输出结果:
Init :
Loop Before : 1
Loop After : 1
Loop Before : 2
Pause : 2
Main : 2
Loop After : 2
Loop Before : 3
Loop After : 3
Loop Before : 4
Pause : 4
Main : 4
Loop After : 4
IDone :
由输出结果得到以下执行流程:
- 执行数组初始化 (Init)
- 在Foreach循环中取出第一个元素(Loop Before : 1)
- 不满足偶数条件(Loop After : 1)
- 在Foreach循环中取出第二个元素(Loop Before : 2)
- 满足偶数条件(Pause : 2)
- 执行yield return 返回 2,并暂停于此
- 主函数输出值(Main : 2)
- 主函数执行下一次循环,从上次暂停的位置开始执行(Loop After : 2)
- …
- 主函数循环结束 (Main : 4)
- 取消最后一次的暂定(Loop After : 4)
- 被调函数执行完成(IDone )
yield return 遵循的规则
由yield return 关键字词组生成的枚举器还会实现IDisposable。此外,在使用这个可枚举对象的地方,还存在一个隐含的try finally块
- yield return 后面要跟一个表达式,该表达式必须能被转换为迭代器所使用的类型
- yield return 只能位于迭代器块中;这个迭代器块既可以放在方法体中,也可以放在运算符函数中,还可以用在访问器中
- yield return 不能放在不安全的块中
- 方法、运算符或访问器的参数不能是ref 或 out
- yield return 语句不能出现在catch块中,也不能出现在带有一个或多个catch子句的try块中
- yield 语句不能出现在匿名方法中
yield return
含有 yield return 语句的函数可以用来做很多事情,比如返回IEnumberable。这就意味着,你可以在任何类型的对象上获得可枚举功能,而且无需编写类型化集合类以及相关的处理代码。
class Program
{
static void Main(string[] args)
{
BinaryTree<int> integers = new BinaryTree<int>();
integers.AddRange(8, 5, 6, 7, 3, 4, 13, 21, 1, 17);
// display conceptual tree
integers.Print();
Console.ReadLine();
Console.Clear();
// write in order
foreach (var item in integers.Inorder)
Console.WriteLine(item);
}
}
// 8
// / \
// 5 13
// / \ \
// 3 6 21
// / \ \ /
// 1 4 7 17
public class Node<T> where T : IComparable<T>
{
public T data;
public Node<T> LeftNode {get;set;}
public Node<T> RightNode {get;set;}
/// <summary>
/// Initializes a new instance of the Node class.
/// </summary>
/// <param name="data"></param>
public Node(T data) => this.data = data;
public override string ToString() => data.ToString();
}
/* 二叉树类,使用yield return 来遍历每个节点。
* 为了能够比较泛型类BinaryTree<T>得数据值,所有类型T都必须是IComparable
*/
public class BinaryTree<T> where T : IComparable<T>
{
private Node<T> root;
public Node<T> Root
{
get { return root; }
set { root = value; }
}
/// <summary>根节点存在得情况下,增加节点;根节点不存在,以当前数据创建根节点</summary>
public void Add(T item)
{
if (root == null)
{
root = new Node<T>(item);
return;
}
Add(item, root);
}
/// <summary>增加子节点</summary>
private void Add(T item, Node<T> node)
{
if (item.CompareTo(node.data) < 0)
{
if (node.LeftNode == null)
node.LeftNode = new Node<T>(item);
else
Add(item, node.LeftNode);
}
else if (item.CompareTo(node.data) > 0)
{
if (node.RightNode == null)
node.RightNode = new Node<T>(item);
else
Add(item, node.RightNode);
}
// silently discard it
}
/// <summary>一次增加多个节点</summary>
public void AddRange(params T[] items)
{
foreach (var item in items)
Add(item);
}
/// <summary>
/// error in displaying tree - 7 is overwritten 17!
/// </summary>
public void Print()
{
Print(root, 0, Console.WindowWidth / 2);
}
public IEnumerable<T> Inorder => GetInOrder(this.root);
IEnumerable<T> GetInOrder(Node<T> node)
{
if (node.LeftNode != null)
foreach (T item in GetInOrder(node.LeftNode))
yield return item;
yield return node.data;
if (node.RightNode != null)
foreach (T item in GetInOrder(node.RightNode))
{
yield return item;
}
}
private void Print(Node<T> item, int depth, int offset)
{
if (item == null)
return;
Console.CursorLeft = offset;
Console.CursorTop = depth;
Console.Write(item.data);
if (item.LeftNode != null)
Print("/", depth + 1, offset - 1);
Print(item.LeftNode, depth + 2, offset - 3);
if (item.RightNode != null)
Print("\\", depth + 1, offset + 1);
Print(item.RightNode, depth + 2, offset + 3);
}
private void Print(string s, int depth, int offset)
{
Console.CursorLeft = offset;
Console.CursorTop = depth;
Console.Write(s);
}
}
yield break
如果因为某种原因,需要在最后得元素被返回之前就结束掉yield return,则可以使用yield break关键字词组。
int depth = 0;
IEnumerable<T> GetInOrder(Node<T> node)
{
if(depth++ > 4) yield break;
if (node.LeftNode != null)
foreach (T item in GetInOrder(node.LeftNode))
yield return item;
yield return node.data;
if (node.RightNode != null)
foreach (T item in GetInOrder(node.RightNode))
{
yield return item;
}
}