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;
}
}
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;
}
}
}
而在我前面的文章中介紹了用尾遞歸的方式計算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;
}
這種實現方式和遞歸函數不同的地方就是它沒有終止條件.上面的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);
}
}
//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;
}
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的文章大家有興趣可以查一下,具體網址我不記得了不好意思).