造假造上瘾——仿造yield关键字(二)
本篇我们讨论for和foreach配合yield的情况。首先看如下代码以及生成的隐藏类。
public static IEnumerable Power(int baseNumber, int highExponent) { int result = 1; for (int counter = 1; counter <= highExponent; counter++) { result = result * baseNumber; yield return result; } }
这是一个算baseNumber的highExponet次幂的方法,在for循环里每计算一次就通过yield返回。在Reflector里,该方法是长成这样的:
再看一下生成的<Power>d_0这个类,因为本文的目的是仿造yield关键字,所以这里仅贴出类定义,具体实现有兴趣各位自己看一下哈哈。
那么紧接着我们就来仿造一个yield类来代替这个生成的类,去掉不必要的接口和多余的字段,干脆连状态机也拿掉,一切从简之后代码如下:
class FakeYieldPower : IEnumerable, IEnumerator { private object current; private int counter = 1; private int result = 1; public int baseNumber; public int highExponent; public bool MoveNext() { if (this.counter > this.highExponent) { return false; } else { this.result *= this.baseNumber; this.current = this.result; this.counter++; return true; } } public IEnumerator GetEnumerator() { return new FakeYieldPower { baseNumber = this.baseNumber, highExponent = this.highExponent }; } public void Reset() { throw new NotSupportedException(); } public object Current { get { return this.current; } } }
也许有人会问,为什么yield返回值一定要同时实现IEnumerable和IEnumerator2个接口。通过IEnumerable接口中的GetEnumerator方法,可以得到一个单纯的实现IEnumerator接口的类,这样划分更清晰,类的职责更明确。其实我也不是很肯定啊,我个人的理解是可以少生成一个隐藏类,同时可以方便地把IEnumerable和IEnumerator转来转去……哎呀,不要扔鸡蛋啊……各位我们先看使用效果……
public static void Process() { // Output: 2 4 8 16 32 64 128 256 foreach (int number in Power2(2, 8)) { Console.Write(number.ToString() + " "); } } public static IEnumerable Power2(int baseNumber, int highExponent) { return new FakeYieldPower { baseNumber= baseNumber, highExponent= highExponent }; }
经本人鉴定是好使的,最显著的变化是我把state状态给删了,这说明yield不是一定要switch case配合state弄成个状态机,里面再插2个goto语句。至于MS为什么这么做,我想是因为该类是自动生成,状态机是一个好选择。按照一定的算法能生成通用的代码。
for循环看完之后接下来我们看一下foreach,这个其实有点蛋疼了,因为能使用foreach的对象,本身就实现了IEnumerable,再把他用yield转成IEnumerable和IEnumerator的混合体返回还是蛮奇怪的。比如:
public IEnumerable<Person> GetPerson() { List<Person> personList = this.GetPersonList(); foreach (var p in personList) { yield return p; } }
生成的代码绝对会让你产生蛋蛋的忧伤,还是伪造yield好了:
public IEnumerable<Person> GetPersonEnumerable() { return this.GetPersonList(); } public IEnumerator<Person> GetPersonEnumerator() { return this.GetPersonList().GetEnumerator(); }
别打……别打了,真的没有的可以伪造的余地啊,本来就已经都写好了啊T_T
还是来测试一下使用效果,必须效果杠杠滴,就跟CCAV一样,都彩排好了,不OK我不放出来:
foreach (var p in person.GetPersonEnumerable()) { Console.WriteLine(p.Name); } var iterator = person.GetPersonEnumerator(); while (iterator.MoveNext()) { Console.WriteLine(iterator.Current.Name); }
至此我们对yield的学习告一段落,话说yield确实省了我们不少行代码,提高了生产了。当然负面的作用就是让初学者云里雾里不明所以。