我与C# yield不能说的秘密
2021-02-14 11:15
那一次的邂逅:
第一次见到yield的时候,内心中充满了各种声音,这是个啥子鬼扯扯的东西?C#有这个破玩意吗?这是一个关键字?按捺不住内心的疑惑,熟练的打开了宇宙第一IDE ------ VS2015.
卧槽,还真有这个关键字.. 看一下解释 "yield 关键字" ,可以,不和我多逼逼! 微软大佬不愧是微软大佬,就是这么高冷.
魂牵梦绕:
面对大佬如此爱答不理的态度,勾引起了我单纯内心的无限遐想,先从字面意思yield理解:产量,产出。难道这个是一个集合应当具有的属性? 找到我们的老朋友List
从yield的字面意思上入手,我们把关注点放在IEnumerable接口上,微软给出的解释是:公开的枚举数,该枚举数支持在非泛型集合上进行简单的迭代.
在C#要对一个集合进行遍历有两种方式:
1.For循环
2.Foreach循环
对于Foreach循环,我们要为这个集合定义一个迭代器才能使用微软大佬给我们提供的福利(Foreach).
眉目传情:
控制不住自己的情绪,事不宜迟,先建立一个Test
class Test: IEnumerable { public IEnumerator GetEnumerator() { throw new NotImplementedException(); } }
从代码中可以看出,要实现IEnumerable接口,必须要实现GetEnumerator()方法, 并且返回的类型是IEnumerator, 所以现在我们要先实现IEnumerator这个接口
class Test: 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
添加实现得到的代码如下:
class Test: 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 parent; private int index;//定义索引 public TestEnumerator(Test 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
测试下代码:
static void Main(string[] args) { int[] values = { 1, 2, 3, 4, 5, 6 }; Testint> t = new Testint>(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 ) { yield return values[index]; } }
简单几行代码就能够完全实现Test
春宵一刻:
利用yield,可以简单的实现Linq中的where条件过滤(惰性过滤)。
在Test
public IEnumerablewhere(Predicate 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 }; Testint> t = new Testint>(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 没有对象的野指针