C#中使用的yield关键字是什么?
在"如何仅显示IList <>的片段"问题中,答案之一具有以下代码片段:
IEnumerable<object> FilteredList() { foreach( object item in FullList ) { if( IsItemInPartialList( item ) yield return item; } }
yield关键字在那里做什么?我已经在几个地方提到过它,还有另一个问题,但是我还没有弄清楚它的实际作用。我习惯于从一个线程向另一个线程屈服的角度来考虑yield,但这在这里似乎无关紧要。
解决方案
回答
迭代。它在"隐藏"状态下创建一个状态机,该状态机会记住我们在该函数的每个其他循环中的位置,然后从那里开始。
回答
这是为对象创建枚举的非常简单的方法。编译器将创建一个包装方法的类,并实现IEnumerable <object>。如果没有yield关键字,则必须创建一个实现IEnumerable <object>的对象。
回答
它产生了不可计数的序列。它实际上是在创建本地IEnumerable序列并将其作为方法结果返回
回答
直观地讲,关键字从函数中返回一个值而不离开它,即在代码示例中,它返回当前的" item"值,然后继续循环。更正式地说,它由编译器用来为迭代器生成代码。迭代器是返回" IEnumerable"对象的函数。 MSDN上有几篇关于它们的文章。
回答
yield关键字实际上在这里做了很多工作。该函数返回一个实现IEnumerable接口的对象。如果调用函数开始在该对象上进行遍历,则再次调用该函数,直到"屈服"为止。这是C2.0中引入的语法糖。在早期版本中,我们必须创建自己的IEnumerable和IEnumerator对象才能执行此类操作。
理解这样的代码的最简单方法是键入示例,设置一些断点,然后看看会发生什么。
尝试逐步执行此操作,例如:
public void Consumer() { foreach(int i in Integers()) { Console.WriteLine(i.ToString()); } } public IEnumerable<int> Integers() { yield return 1; yield return 2; yield return 4; yield return 8; yield return 16; yield return 16777216; }
在逐步浏览示例时,我们会发现对Integers()的第一次调用返回1. 第二次调用返回2,并且不再再次执行" yield return 1"行。
这是一个真实的例子
public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms) { using (var connection = CreateConnection()) { using (var command = CreateCommand(CommandType.Text, sql, connection, parms)) { command.CommandTimeout = dataBaseSettings.ReadCommandTimeout; using (var reader = command.ExecuteReader()) { while (reader.Read()) { yield return make(reader); } } } } }
回答
最近,Raymond Chen也对yield关键字进行了一系列有趣的文章。
- C#中迭代器的实现及其结果(第1部分)
- C#中迭代器的实现及其结果(第2部分)
- C#中迭代器的实现及其后果(第3部分)
- C#中迭代器的实现及其后果(第4部分)
虽然名义上它用于轻松实现迭代器模式,但可以将其推广到状态机中。引用Raymond毫无意义,最后一部分也链接到其他用途(但是Entin博客中的示例特别好,显示了如何编写异步安全代码)。
回答
它正在尝试带来一些Ruby Goodness :)
概念:这是一些示例Ruby代码,可打印出数组的每个元素
rubyArray = [1,2,3,4,5,6,7,8,9,10] rubyArray.each{|x| puts x # do whatever with x }
数组的每个方法实现都将控制权交给调用者(" puts x"),而数组的每个元素都以x整齐地表示。然后,调用者可以执行x所需的任何操作。
但是,.Net并非一路走来。.似乎将yield与IEnumerable耦合在一起,以某种方式迫使我们在调用方中编写一个foreach循环,如Mendelt的响应所示。少一点优雅。
//calling code foreach(int i in obCustomClass.Each()) { Console.WriteLine(i.ToString()); } // CustomClass implementation private int[] data = {1,2,3,4,5,6,7,8,9,10}; public IEnumerable<int> Each() { for(int iLooper=0; iLooper<data.Length; ++iLooper) yield return data[iLooper]; }