yield是c#2.0中引入的关键词,以前只是匆匆看过它的用法,觉得不过是个语法糖.这两天突然对yield产生了兴趣进行了一些小实验.有了一些心得放在这里与大家共享.
yield确实是一块非常好的语法糖,方便我们写iterator.比如我要给
Tuple加一个迭代功能就非常容易.
//
我以Tuple<T1, T2, T3>举例
public class Tuple < T1, T2, T3 > : Tuple < T1, T2 > ,IEnumerable
... {
T3 _t3;
public Tuple(T1 t1, T2 t2, T3 t3) : base(t1, t2)
...{
this._t3 = t3;
}
//很方便的加入迭代功能
public override IEnumerator GetEnumerator()
...{
yield return this.Item_1;
yield return this.Item_2;
yield return this._t3;
}
}
public class Tuple < T1, T2, T3 > : Tuple < T1, T2 > ,IEnumerable
... {
T3 _t3;
public Tuple(T1 t1, T2 t2, T3 t3) : base(t1, t2)
...{
this._t3 = t3;
}
//很方便的加入迭代功能
public override IEnumerator GetEnumerator()
...{
yield return this.Item_1;
yield return this.Item_2;
yield return this._t3;
}
}
yield return相当于执行了一次MoveNext(); 并返回Current的过程.所以在上面的代码里我才能非常容易的给Tuple加上迭代功能.
yield不仅能够简化实现迭代功能,它还能帮助我们换种方式实现递归.来看看下面这个演示函数它非常容易的实现了一个中序遍历
IEnumerable
<
T
>
ScanInOrder(Node
<
T
>
root)
... {
if(root.LeftNode != null)
...{
foreach(T item in ScanInOrder(root.LeftNode))
...{
yield return item;
}
}
yield return root.Item;
if(root.RightNode != null)
...{
foreach(T item in ScanInOrder(root.RightNode))
...{
yield return item;
}
}
}
... {
if(root.LeftNode != null)
...{
foreach(T item in ScanInOrder(root.LeftNode))
...{
yield return item;
}
}
yield return root.Item;
if(root.RightNode != null)
...{
foreach(T item in ScanInOrder(root.RightNode))
...{
yield return item;
}
}
}
而在我前面的
文章中介绍了用尾递归的方式计算Fibonacci数列的方法.在这里我要用yield实现一个不同版本.
//
使用Yield计算Fibonacci数列
public long Fib_Yield( int n)
... {
long num = 0;
int index = 3;
foreach (long b in this.Yield(1, 1))
...{
if (index == n)
...{
num = b;
break;
}
else
++index;
}
return num;
}
public IEnumerable < long > Yield( long b1, long b2)
... {
yield return b1 + b2;
foreach (long b in Yield(b2, b1 + b2))
yield return b;
}
public long Fib_Yield( int n)
... {
long num = 0;
int index = 3;
foreach (long b in this.Yield(1, 1))
...{
if (index == n)
...{
num = b;
break;
}
else
++index;
}
return num;
}
public IEnumerable < long > Yield( long b1, long b2)
... {
yield return b1 + b2;
foreach (long b in Yield(b2, b1 + b2))
yield return b;
}
这种实现方式和递归函数不同的地方就是它没有终止条件.上面的Yield函数实际上能够计算一个无限数列(这有点像FP中的惰性计算)而Fib_Yield函数才是真正实现业务逻辑的地方.这是不是有点业务逻辑和通用算法分离的感觉.再让我们看一个例子.
在写WebForm或WinForm程序的时候我们经常会应用一个技巧就是把业务实体对象(Entity Object)的数据赋值给UI上的控件.所以我们就会写一个递归遍历所有控件的函数类似于如下函数:
public
void
SetValue(Control ctl, EntityObject obj)
... {
foreach(Control c in ctl.Controls)
...{
if(c is TextBox)
...{
//利用反射将obj的数据赋值给该控件
}
if(c is CheckBox)
...{
//利用反射将obj的数据赋值给该控件
}
//......
if(c.HasControls())
SetValue(c, obj);
}
}
... {
foreach(Control c in ctl.Controls)
...{
if(c is TextBox)
...{
//利用反射将obj的数据赋值给该控件
}
if(c is CheckBox)
...{
//利用反射将obj的数据赋值给该控件
}
//......
if(c.HasControls())
SetValue(c, obj);
}
}
//GetValue函数与此相仿
而用yield就会变成这个样子(注:关于Tuple的HasType()函数请看这里)
public
IEnumerable
<
Control
>
Iterator(Control baseCtl, Tuple t)
... {
foreach(Control c in baseCtl.Controls)
...{
if (!t.HasType(c))
...{
foreach (Control c1 in Iterator(c, t))
yield return c1;
}
... {
foreach(Control c in baseCtl.Controls)
...{
if (!t.HasType(c))
...{
foreach (Control c1 in Iterator(c, t))
yield return c1;
}
else //07.12.24加
yield return c;
}
}
public void SetValue(EntityObject obj)
... {
foreach(Control c in Iterator(this, new Tuple<TextBox, CheckBox, Button>()))
...{
//操作c
}
}
yield return c;
}
}
public void SetValue(EntityObject obj)
... {
foreach(Control c in Iterator(this, new Tuple<TextBox, CheckBox, Button>()))
...{
//操作c
}
}
这样我就可以把Iterator函数放到基础库中,而把对WebForm和WinForm控件的赋值操作放到业务逻辑层中.实现了一定的解耦.
看来yield还有待挖掘的潜力,特别是它可以把函数中的临时变量保存到栈上的能力,我觉得可以在多线程编程中应用yield(微软msdn杂志有一篇介绍应用yield实现异步IO的文章大家有兴趣可以查一下,具体网址我不记得了不好意思).