造假造上瘾——仿造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确实省了我们不少行代码,提高了生产了。当然负面的作用就是让初学者云里雾里不明所以。

        代码下载

        

posted @ 2012-12-13 23:05  楼上那个蜀黍  阅读(1530)  评论(1编辑  收藏  举报