我与C# yield不能说的秘密

那一次的邂逅:

第一次见到yield的时候,内心中充满了各种声音,这是个啥子鬼扯扯的东西?C#有这个破玩意吗?这是一个关键字?按捺不住内心的疑惑,熟练的打开了宇宙第一IDE ------ VS2015.

卧槽,还真有这个关键字..  看一下解释 "yield 关键字" ,可以,不和我多逼逼! 微软大佬不愧是微软大佬,就是这么高冷.

魂牵梦绕:

面对大佬如此爱答不理的态度,勾引起了我单纯内心的无限遐想,先从字面意思yield理解:产量,产出。难道这个是一个集合应当具有的属性? 找到我们的老朋友List<T>:

从yield的字面意思上入手,我们把关注点放在IEnumerable接口上,微软给出的解释是:公开的枚举数,该枚举数支持在非泛型集合上进行简单的迭代.

在C#要对一个集合进行遍历有两种方式:

1.For循环

2.Foreach循环

对于Foreach循环,我们要为这个集合定义一个迭代器才能使用微软大佬给我们提供的福利(Foreach).

眉目传情:

控制不住自己的情绪,事不宜迟,先建立一个Test<T>类来实现IEnumerable来试一试:

class Test<T> : IEnumerable
        {

            public IEnumerator GetEnumerator()
            {
                throw new NotImplementedException();
            }

        }

从代码中可以看出,要实现IEnumerable接口,必须要实现GetEnumerator()方法, 并且返回的类型是IEnumerator, 所以现在我们要先实现IEnumerator这个接口

class Test<T> : IEnumerable
        {

            public IEnumerator GetEnumerator()
            {
                throw new NotImplementedException();
            }

            class TestEnumerator : IEnumerator
            {
                public object Current
                {
                    get
                    {
                        throw new NotImplementedException();
                    }
                }

                public bool MoveNext()
                {
                    throw new NotImplementedException();
                }

                public void Reset()
                {
                    throw new NotImplementedException();
                }
            }
        }

实现了IEnumerator接口,必须要实现 Current属性,以及MoveNext() 和 Reset()方法。 并且使用了嵌套类TestEnumerator,为什么要使用嵌套类呢,客官莫急莫急,且听臣娓娓道来.

Current : 简单的说就是保存集合当前的值,并且只有get权限.

MoveNext():返回的是个bool类型的值,用来判断集合是否还有下一个元素。 如果集合还有元素则返回true,反之亦然。

Rest():对集合进行重置操作.

为什么要使用嵌套类呢?  因为我们要对Test<T>类的实例进行操作,如果我们使用了一个顶级类,那么会得不到相应的数据(没有权限查看).

添加实现得到的代码如下:

class Test<T> : IEnumerable
        {
            private T[] values; //用于接收传递的数组
            public Test()
            {

            }
            public Test(T[] values) //构造函数初始化
            {
                this.values = values;
            }

            public IEnumerator GetEnumerator()
            {
                return new TestEnumerator(this); // 因为需要得到Test 实列的values.(所以使用嵌套类 有权限能访问到values的值)
            }

            class TestEnumerator : IEnumerator
            {
                private Test<T> parent;
                private int index;//定义索引
                public TestEnumerator(Test<T> parent)
                {
                    this.parent = parent;
                    index = -1;// 在foreach的时候 会先去调用 GetEnumerator()方法, 所以先给定一个index=-1 初始化。
                }
                public object Current  //得到当前集合的值
                {
                    get
                    {
                        if (index == -1 || index == parent.values.Length)
                        {
                            throw new InvalidOperationException();
                        }
                        return this.parent.values[index];
                    }
                }

                public bool MoveNext() //是否还有剩余元素
                {
                    if (index != parent.values.Length)
                    {
                        index++;
                    }
                    return index < parent.values.Length;
                }

                public void Reset()
                {
                    index = -1; // 初始化索引值;
                }
            }
        }

在下比较懒,对于Test<T>的内部values只用了数组来存,有时间 我会更新成用链表的数据结构来实现这个方法,但是这个不是今天的重点..

测试下代码:

static void Main(string[] args)
        {
            int[] values = { 1, 2, 3, 4, 5, 6 };
            Test<int> t = new Test<int>(values);

            // 在遍历实列的时候
            // 1.会先去找到GetEnumerator()方法
            // 2.在去初始化 实现IEnumerator接口的类,并且初始化
            // 3. 在去MoveNext()判断是否还有下一项
            // 4.输出Current属性, 此时代表的就是item
            foreach (var item in t)
            {
                Console.WriteLine(item);  // 1,2,3,4,5,6
            }
            Console.ReadKey();
        }

初次相识:

说了半天,怎么看不到yield的影子,故弄玄虚,博主你是不是个睿智啊???   不好意思,让各位大爷久等了,现在闪亮的请出我们今天的主角 ”yield“。

在C#1.0要需要手动实现迭代器,洋洋洒洒几十行的代码,微软大佬你就不能对小弟好一些,使用写短小干练的方式吗?  于是乎,大佬决定相应小弟们的号召,使用关键字”yield“ 对迭代器进行封装.. (微软大佬就是好,没事就给小弟们吃糖(语法糖),牙齿都给我吃的曲黑!!)

不多逼逼,简单粗暴点,直接上代码:

重新实现GetEnumerator()方法:

   public IEnumerator GetEnumerator()
            {
                for (var index = 0; index < values.Length; index++)
                {
                    yield return values[index];
                }
            }

简单几行代码就能够完全实现Test<T>类所需要的功能。方法看起来很普通,除了使用了yield return。这条语句告诉编译器这不是一个普通的方法,而是一个需要执行的迭代块(yield block),他返回一个IEnumerator对象,你能够使用迭代块来执行迭代方法并返回一个IEnumerable需要实现的类型,IEnumerator或者对应的泛型。如果实现的是非泛型版本的接口,迭代块返的yield type是Object类型,否则返回的是相应的泛型类型。例如,如果方法实现IEnumerable<String>接口,那么yield返回的类型就是String类型。 在迭代块中除了yield return外,不允许出现普通的return语句。块中的所有yield return 语句必须返回和块的最后返回类型兼容的类型。举个例子,如果方法定义需要返回IEnumeratble<String>类型的话,不能yield return 1 。 需要强调的一点是,对于迭代块,虽然我们写的方法看起来像是在顺序执行,实际上我们是让编译器来为我们创建了一个状态机。这就是在C#1中我们书写的那部分代码---调用者每次调用只需要返回一个值,因此我们需要记住最后一次返回值时,在集合中位置。 当编译器遇到迭代块是,它创建了一个实现了状态机的内部类。这个类记住了我们迭代器的准确当前位置以及本地变量,包括参数。这个类有点类似与我们之前手写的那段代码,他将所有需要记录的状态保存为实例变量。

春宵一刻:

利用yield,可以简单的实现Linq中的where条件过滤(惰性过滤)。

在Test<T>中添加where方法:

public IEnumerable<T> where(Predicate<T> predicate)
            {
                foreach (T item in values)
                {
                    if (predicate(item))
                    {
                        yield return item;
                    }
                }
            }

测试:

static void Main(string[] args)
        {
            int[] values = { 1, 2, 3, 4, 5, 6 };
            Test<int> t = new Test<int>(values);

            foreach (var item in t.where(x => x < 5))
            {
                Console.WriteLine(item);  // 1,2,3,4
            }
            Console.ReadKey();
        }

贤者时间:

C#对许多设计模式进行了间接的实现,使得实现这些模式变得很容易。相对来针对某一特定的设计模式直接实现的的特性比较少。从foreach代码中看出,C#1对迭代器模式进行了直接的支持,但是没有对进行迭代的集合进行有效的支持。对集合实现一个正确的IEnumerable很耗时,容易出错也很枯燥。在C#2中,编译器为我们做了很多工作,为我们实现了一个状态机来实现迭代。

代码中还有些瑕疵的地方,比如没有处理异常,没有使用泛型的接口等。还有yield break的时候,需要释放资源的问题,具体的细节,有时间在写把!

微软大佬,法力无边,使我螺旋升天。--------- by 没有对象的野指针

原文地址:https://www.cnblogs.com/yezhizhen/p/8449355.html

时间: 2024-11-13 11:13:58

我与C# yield不能说的秘密的相关文章

不能说的秘密

距离<不能说的秘密>这部电影已经整整十年了,第一次看的时候是中学时候老师用U盘从家里拷贝来的副本,大家找了周末的课余时间用教师的多媒体放映的.当时还不怎么能够欣赏音乐,尤其是古典音乐,所以有些电影中出现的曲子会觉得比较炫酷,比如在斗琴中的曲子.不过在小雨自己弹的时候,以及末尾时候虚弱的路小雨趴在桌子上,用最后的力气写下“我爱你”的时候,那段配乐煽情又凄美.现在觉得很经典.后面还出现了<蒲公英的约定>作为配乐.电影本身不知道怎么样,觉得场景很美好.前段时间看周杰伦说自己母校,谈到阳光

【娱乐向】c/c++语言不能说的秘密

大家都爱c/c++不是吗?  c/c++有着丰富的模板库,such as<map><stack><queue><ext\pb_ds\priority_queue.hpp>...真是喜闻乐见啊,但是谁知道呢,在C的内心最深处,还隐藏者那些不能说的秘密. SECRET _1 (数组魔法,规整的数组有一颗癫狂的心) 我们来看这样的几行代码 1 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 2 f

不能说的秘密之紫发晶之谜

以前只顾欣赏三轮紫骨干水晶的美丽,并没有去思索它的功能. 三轮紫骨干通常包含有三种颜色,即黑色.红色和紫色,所对应的三轮,自下而上分别是:海底轮.生殖轮和眉心轮.也就是说,它对应了人体最初和最后的轮脉! 海底轮是人体的第一个轮脉,位于脊椎底端(在不同的理论中,其位置稍有不同,这里取<改变,从心开始>一书中的说法)被看作是生命的原点,是生命力的起点.它与生命喜悦感升起的第二轮生殖轮非常靠近,轮脉振动频率都比较低,属于物质界. 眉心轮是第六轮,是位于人体的最后一个轮脉,也是一个有关灵性的轮脉. 海

红楼梦里王夫人为何让玉钏儿吃双份儿工资?有一个不能说的秘密

王夫人是贾府的当家主母,她所有行为都不是无意义的,她突然间把玉钏儿的月钱提升到二两银子,这是一个很敏感的数字,因为姨娘们的月钱也是这个数.王夫人这样做到底是什么意思呢? 王夫人听了,又想一想,道:"也罢,这个分例只管关了来,不用补人,就把这一两银子给他妹妹玉钏儿罢.他姐姐伏侍了我一场,没个好结果,剩下他妹妹跟着我,吃个双分子也不为过逾了."--王夫人问道:"正要问你,如今赵姨娘周姨娘的月例多少?"凤姐道:"那是定例,每人二两.赵姨娘有环兄弟的二两,共是四两

别踩白块儿

相信大家对这个游戏并不陌生.这个游戏可玩性不错,就是有点闹心,一点小错误又得重新来过.可所谓一失足成千古恨啊! 不过我最近在和朋友争论这个游戏里的钢琴音效的问题.我认为那是一首首歌曲的调,他却不这么认为(可能有点笨吧).因为我玩的时候明明可以听到是歌曲的音调,还可以跟着哼出来.这里我搜集了一下这里面的歌曲,有我听出来的,也有我在网上搜的.这些音乐随机出现在一局游戏里,当你足够厉害弹完一首歌曲,它会在随机地出现一首(难道没有尽头?)笔者反正只连续听过两首歌曲.不过随着游戏的进行,节奏越来越快,有些

于宙:我们这一代人的困惑

导读:本文是于宙在TEDx大会上的演讲,这篇文章有点长,不过非常值得你花20分钟把它看完.本周如果只能推荐一篇文章,那么强烈建议你读读这一篇,“一个有故事的大叔说的实在话”. 以下是演讲全文: 大家下午好, 很荣幸能够参加本次TEDx大会,非常感谢东北财经大学TED团队和华臣影城为我们提供这样一个交流的机会. 自我介绍 我是大连人,高中就读于大连市二十四中.因为当时学习十分不努力,所以高中毕业之后选择了出国留学,这其实是很多本科出国留学的人不能说的秘密,辗转了几个学校,最终毕业于美国印第安纳大学

[转自互联网][怀疑是自动写作]

我知道我的电脑被我最爱的人一定改编过一些东西,他学习计算机专业,他的同学也会改编程序,qq刚出来的时候,他的同学在网吧弄程序冲年费黄砖绿砖,走了之后,网吧系统瘫痪!而我的问题是我进入的每一个网站都是早已经被设定过得网站,比如百度,优酷那些!手机也是!在我心里,我知道他是个天才,他能看懂一些类似西欧文的东西,他可以一健掌控多号,他可以还原之前的足迹,他可以随意变换ip地址!他可以用到国外的网站,他可以制造网页!可以在云端和你聊天.可以用我小号和我大号聊天!一条被撤回的消息意外的落到了小号里面!我才

也许微信正在将场景化应用带到你的身边

也许微信正在将场景化应用带到你的身边 对于微信小程序的未来,我一直保持着一种较为克制的态度.原因很简单,微信一直没有公布小程序的入口在哪里,搜索机制到底是什么.而这两个问题恰恰是小程序生态里最最重要的. 小程序并不新鲜,模式上先有百度轻应用,后有支付宝的各类小服务,再来还有腾讯自家QQ右下角的应用宝:技术上也就是FaceBook RN的那一套.一个技术上无创新,形式上无创意的事物,凭什么勾起了开发者们疯狂的崇拜,甚至引起了互联网圈子的震动?答案还是在于微信这个超级应用的流量入口和关系链上,如果是

【Python】Java程序员学习Python(二)— 开发环境搭建

巧妇难为无米之炊,我最爱的还是鸡蛋羹,因为我和鸡蛋羹有段不能说的秘密. 不管学啥,都要有环境,对于程序员来说搭建个开发环境应该不是什么难题.按顺序一步步来就可以,我也只是记录我的安装过程,你也可以滴. 一.准备Java环境 我已经说过了,其实我是一个Java程序员,所以学习过程中会有很多Java相关的内容和对比.先介绍下我的基本情况 jdk1.8 eclipse即可,版本最新的 怎么安装java,配置环境变量什么的,我都不会再说了,我这不是小白教程,我相信具备一定的能力. 二.准备Python环