In the How Can I Expose Only a Fragment of IList<> question one of the answers had the following code snippet:
在我如何只公开IList的片段<>问题中,其中一个答案有以下代码片段:
IEnumerable<object> FilteredList()
{
foreach( object item in FullList )
{
if( IsItemInPartialList( item )
yield return item;
}
}
What does the yield keyword do there? I've seen it referenced in a couple places, and one other question, but I haven't quite figured out what it actually does. I'm used to thinking of yield in the sense of one thread yielding to another, but that doesn't seem relevant here.
yield关键字有什么作用?我已经看到它在几个地方被引用,另外一个问题,但我还没弄清楚它实际上做了什么。我习惯于在一个线程产生另一个线程的意义上考虑收益率,但这似乎并不重要。
16 个解决方案
#1
609
The yield keyword actually does quite a lot here. The function returns an object that implements the IEnumerable interface. If a calling function starts foreach-ing over this object the function is called again until it "yields". This is syntactic sugar introduced in C# 2.0. In earlier versions you had to create your own IEnumerable and IEnumerator objects to do stuff like this.
yield关键字实际上在这里做了很多。该函数返回一个实现IEnumerable接口的对象。如果调用函数开始对该对象进行预处理,则再次调用该函数,直到它“产生”为止。这是C#2.0中引入的语法糖。在早期版本中,您必须创建自己的IEnumerable和IEnumerator对象来执行此类操作。
The easiest way understand code like this is to type in an example, set some breakpoints and see what happens.
理解这样的代码的最简单方法是输入一个示例,设置一些断点并查看会发生什么。
Try stepping through this for example:
尝试单步执行此操作,例如:
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;
}
When you step through the example you'll find the first call to Integers() returns 1. The second call returns 2 and the line "yield return 1" is not executed again.
当您单步执行该示例时,您将发现对Integers()的第一次调用返回1.第二次调用返回2并且不再执行“yield return 1”行。
Here is a real-life example
这是一个现实生活中的例子
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);
}
}
}
}
}
#2
334
Iteration. It creates a state machine "under the covers" that remembers where you were on each additional cycle of the function and picks up from there.
迭代。它创建了一个“在幕后”的状态机,它可以记住你在函数的每个附加周期中的位置并从中获取。
#3
163
Yield has two great uses,
产量有两大用途,
-
It helps to provide custom iteration without creating temp collections.
它有助于提供自定义迭代而无需创建临时集合。
-
It helps to do stateful iteration.
它有助于进行有状态迭代。
In order to explain above two points more demonstratively, I have created a simple video you can watch it here
为了更具说明性地解释上述两点,我创建了一个简单的视频,你可以在这里观看
#4
125
Recently Raymond Chen also ran an interesting series of articles on the yield keyword.
最近Raymond Chen还在yield关键字上发表了一系列有趣的文章。
- The implementation of iterators in C# and its consequences (part 1)
- The implementation of iterators in C# and its consequences (part 2)
- The implementation of iterators in C# and its consequences (part 3)
- The implementation of iterators in C# and its consequences (part 4)
C#中迭代器的实现及其后果(第1部分)
C#中迭代器的实现及其后果(第2部分)
C#中迭代器的实现及其后果(第3部分)
C#中迭代器的实现及其后果(第4部分)
While it's nominally used for easily implementing an iterator pattern, but can be generalized into a state machine. No point in quoting Raymond, the last part also links to other uses (but the example in Entin's blog is esp good, showing how to write async safe code).
虽然它名义上用于轻松实现迭代器模式,但可以推广到状态机。没有必要引用Raymond,最后一部分也链接到其他用途(但Entin的博客中的示例是esp好,显示如何编写异步安全代码)。
#5
53
At first sight, yield return is a .NET sugar to return an IEnumerable.
乍一看,yield return是一个返回IEnumerable的.NET糖。
Without yield, all the items of the collection are created at once:
如果没有产量,则立即创建集合的所有项目:
class SomeData
{
public SomeData() { }
static public IEnumerable<SomeData> CreateSomeDatas()
{
return new List<SomeData> {
new SomeData(),
new SomeData(),
new SomeData()
};
}
}
Same code using yield, it returns item by item:
使用yield的相同代码,它逐项返回:
class SomeData
{
public SomeData() { }
static public IEnumerable<SomeData> CreateSomeDatas()
{
yield return new SomeData();
yield return new SomeData();
yield return new SomeData();
}
}
The advantage of using yield is that if the function consuming your data simply needs the first item of the collection, the rest of the items won't be created.
使用yield的优点是,如果消耗数据的函数只需要集合的第一项,则不会创建其余项。
The yield operator allows the creation of items as it is demanded. That's a good reason to use it.
yield操作符允许根据需要创建项目。这是使用它的一个很好的理由。
#6
30
yield return
is used with enumerators. On each call of yield statement, control is returned to the caller but it ensures that the callee's state is maintained. Due to this, when the caller enumerates the next element, it continues execution in the callee method from statement immediately after the yield
statement.
yield return与枚举器一起使用。在每次调用yield语句时,控制权返回给调用者,但它确保维持被调用者的状态。因此,当调用者枚举下一个元素时,它会继续在yield语句之后的语句中的callee方法中执行。
Let us try to understand this with an example. In this example, corresponding to each line I have mentioned the order in which execution flows.
让我们试着通过一个例子来理解这一点。在这个例子中,对应于每一行,我提到了执行流程的顺序。
static void Main(string[] args)
{
foreach (int fib in Fibs(6))//1, 5
{
Console.WriteLine(fib + " ");//4, 10
}
}
static IEnumerable<int> Fibs(int fibCount)
{
for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
{
yield return prevFib;//3, 9
int newFib = prevFib + currFib;//6
prevFib = currFib;//7
currFib = newFib;//8
}
}
Also, the state is maintained for each enumeration. Suppose, I have another call to Fibs()
method then the state will be reset for it.
此外,每个枚举都保持状态。假设,我再次调用Fibs()方法,然后状态将被重置。
#7
29
Intuitively, the keyword returns a value from the function without leaving it, i.e. in your code example it returns the current item
value and then resumes the loop. More formally, it is used by the compiler to generate code for an iterator. Iterators are functions that return IEnumerable
objects. The MSDN has several articles about them.
直观地,关键字从函数返回一个值而不离开它,即在代码示例中它返回当前项值,然后恢复循环。更正式地说,编译器使用它来为迭代器生成代码。迭代器是返回IEnumerable对象的函数。 MSDN有几篇关于它们的文章。
#8
21
A list or array implementation loads all of the items immediately whereas the yield implementation provides a deferred execution solution.
列表或数组实现立即加载所有项,而yield实现提供延迟执行解决方案。
In practice, it is often desirable to perform the minimum amount of work as needed in order to reduce the resource consumption of an application.
在实践中,通常希望根据需要执行最少量的工作以减少应用程序的资源消耗。
For example, we may have an application that process millions of records from a database. The following benefits can be achieved when we use IEnumerable in a deferred execution pull-based model:
例如,我们可能有一个处理来自数据库的数百万条记录的应用程序。当我们在延迟执行基于拉的模型中使用IEnumerable时,可以实现以下好处:
- Scalability, reliability and predictability are likely to improve since the number of records does not significantly affect the application’s resource requirements.
- Performance and responsiveness are likely to improve since processing can start immediately instead of waiting for the entire collection to be loaded first.
- Recoverability and utilisation are likely to improve since the application can be stopped, started, interrupted or fail. Only the items in progress will be lost compared to pre-fetching all of the data where only using a portion of the results was actually used.
- Continuous processing is possible in environments where constant workload streams are added.
可扩展性,可靠性和可预测性可能会提高,因为记录数量不会显着影响应用程序的资源需求。
性能和响应性可能会提高,因为处理可以立即开始,而不是等待首先加载整个集合。
由于应用程序可以停止,启动,中断或失败,因此可恢复性和利用率可能会提高。与预取所有仅使用部分结果的数据相比,只有正在进行的项目将丢失。
在添加常量工作负载流的环境中可以进行连续处理。
Here is a comparison between build a collection first such as a list compared to using yield.
下面是构建集合的第一个例如列表与使用yield之间的比较。
List Example
public class ContactListStore : IStore<ContactModel>
{
public IEnumerable<ContactModel> GetEnumerator()
{
var contacts = new List<ContactModel>();
Console.WriteLine("ContactListStore: Creating contact 1");
contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
Console.WriteLine("ContactListStore: Creating contact 2");
contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
Console.WriteLine("ContactListStore: Creating contact 3");
contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
return contacts;
}
}
static void Main(string[] args)
{
var store = new ContactListStore();
var contacts = store.GetEnumerator();
Console.WriteLine("Ready to iterate through the collection.");
Console.ReadLine();
}
Console Output
ContactListStore: Creating contact 1
ContactListStore: Creating contact 2
ContactListStore: Creating contact 3
Ready to iterate through the collection.
控制台输出ContactListStore:创建联系人1 ContactListStore:创建联系人2 ContactListStore:创建联系人3准备迭代集合。
Note: The entire collection was loaded into memory without even asking for a single item in the list
注意:整个集合都已加载到内存中,甚至没有要求列表中的单个项目
Yield Example
public class ContactYieldStore : IStore<ContactModel>
{
public IEnumerable<ContactModel> GetEnumerator()
{
Console.WriteLine("ContactYieldStore: Creating contact 1");
yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
Console.WriteLine("ContactYieldStore: Creating contact 2");
yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
Console.WriteLine("ContactYieldStore: Creating contact 3");
yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
}
}
static void Main(string[] args)
{
var store = new ContactYieldStore();
var contacts = store.GetEnumerator();
Console.WriteLine("Ready to iterate through the collection.");
Console.ReadLine();
}
Console Output
Ready to iterate through the collection.
控制台输出准备迭代整个集合。
Note: The collection wasn't executed at all. This is due to the "deferred execution" nature of IEnumerable. Constructing an item will only occur when it is really required.
注意:集合根本没有执行。这是由于IEnumerable的“延迟执行”性质。只有在真正需要时才会构建项目。
Let's call the collection again and obverse the behaviour when we fetch the first contact in the collection.
让我们再次调用该集合,并在我们获取集合中的第一个联系人时讨论该行为。
static void Main(string[] args)
{
var store = new ContactYieldStore();
var contacts = store.GetEnumerator();
Console.WriteLine("Ready to iterate through the collection");
Console.WriteLine("Hello {0}", contacts.First().FirstName);
Console.ReadLine();
}
Console Output
Ready to iterate through the collection
ContactYieldStore: Creating contact 1
Hello Bob
控制台输出准备迭代集合ContactYieldStore:创建联系人1 Hello Bob
Nice! Only the first contact was constructed when the client "pulled" the item out of the collection.
太好了!当客户端将项目“拉出”集合时,仅构建了第一个联系人。
#9
14
Here is a simple way to understand the concept: The basic idea is, if you want a collection that you can use "foreach
" on, but gathering the items into the collection is expensive for some reason (like querying them out of a database), AND you will often not need the entire collection, then you create a function that builds the collection one item at a time and yields it back to the consumer (who can then terminate the collection effort early).
这是一个理解这个概念的简单方法:基本思想是,如果你想要一个可以使用“foreach”的集合,但是由于某些原因(比如从数据库中查询它们)将项目收集到集合中是昂贵的,并且您通常不需要整个集合,然后您创建一个函数,一次构建一个项目集合并将其返回给消费者(然后可以提前终止集合工作)。
Think of it this way: You go to the meat counter and want to buy a pound of sliced ham. The butcher takes a 10-pound ham to the back, puts it on the slicer machine, slices the whole thing, then brings the pile of slices back to you and measures out a pound of it. (OLD way). With yield
, the butcher brings the slicer machine to the counter, and starts slicing and "yielding" each slice onto the scale until it measures 1-pound, then wraps it for you and you're done. The Old Way may be better for the butcher (lets him organize his machinery the way he likes), but the New Way is clearly more efficient in most cases for the consumer.
可以这样想:你去肉类柜台,想要买一磅切好的火腿。屠夫把一个10磅重的火腿放在后面,把它放在切片机上,切成整片,然后将一堆切片带回给你,然后测出一磅。 (旧方式)。随着产量的提高,屠夫将切片机带到柜台,然后开始切片并“切割”每个切片到秤上,直到它测量到1磅,然后为你包装它就完成了。对于屠夫而言,旧方式可能更好(让他按照自己喜欢的方式组织他的机器),但对于消费者而言,新方式在大多数情况下显然更有效。
#10
11
The yield
keyword allows you to create an IEnumerable<T>
in the form on an iterator block. This iterator block supports deferred executing and if you are not familiar with the concept it may appear almost magical. However, at the end of the day it is just code that executes without any weird tricks.
yield关键字允许您在迭代器块上的表单中创建IEnumerable
An iterator block can be described as syntactic sugar where the compiler generates a state machine that keeps track of how far the enumeration of the enumerable has progressed. To enumerate an enumerable, you often use a foreach
loop. However, a foreach
loop is also syntactic sugar. So you are two abstractions removed from the real code which is why it initially might be hard to understand how it all works together.
迭代器块可以被描述为语法糖,其中编译器生成状态机,该状态机跟踪可枚举的枚举进展的程度。要枚举可枚举,您经常使用foreach循环。然而,foreach循环也是语法糖。因此,您从实际代码中删除了两个抽象,这就是为什么它最初可能很难理解它们如何一起工作。
Assume that you have a very simple iterator block:
假设您有一个非常简单的迭代器块:
IEnumerable<int> IteratorBlock()
{
Console.WriteLine("Begin");
yield return 1;
Console.WriteLine("After 1");
yield return 2;
Console.WriteLine("After 2");
yield return 42;
Console.WriteLine("End");
}
Real iterator blocks often have conditions and loops but when you check the conditions and unroll the loops they still end up as yield
statements interleaved with other code.
真正的迭代器块通常具有条件和循环,但是当您检查条件并展开循环时,它们仍然最终作为与其他代码交错的yield语句。
To enumerate the iterator block a foreach
loop is used:
要枚举迭代器块,使用foreach循环:
foreach (var i in IteratorBlock())
Console.WriteLine(i);
Here is the output (no surprises here):
这是输出(这里没有惊喜):
Begin 1 After 1 2 After 2 42 End
As stated above foreach
is syntactic sugar:
如上所述,foreach是句法糖:
IEnumerator<int> enumerator = null;
try
{
enumerator = IteratorBlock().GetEnumerator();
while (enumerator.MoveNext())
{
var i = enumerator.Current;
Console.WriteLine(i);
}
}
finally
{
enumerator?.Dispose();
}
In an attempt to untangle this I have crated a sequence diagram with the abstractions removed:
为了解开这个问题,我创建了一个删除了抽象的序列图:
The state machine generated by the compiler also implements the enumerator but to make the diagram more clear I have shown them as separate instances. (When the state machine is enumerated from another thread you do actually get separate instances but that detail is not important here.)
编译器生成的状态机也实现了枚举器,但为了使图更清晰,我将它们显示为单独的实例。 (当从另一个线程枚举状态机时,您实际上会获得单独的实例,但这里的详细信息并不重要。)
Every time you call your iterator block a new instance of the state machine is created. However, none of your code in the iterator block is executed until enumerator.MoveNext()
executes for the first time. This is how deferred executing works. Here is a (rather silly) example:
每次调用迭代器块时,都会创建一个新的状态机实例。但是,在第一次执行enumerator.MoveNext()之前,迭代器块中的所有代码都不会执行。这是延迟执行的工作原理。这是一个(相当愚蠢)的例子:
var evenNumbers = IteratorBlock().Where(i => i%2 == 0);
At this point the iterator has not executed. The Where
clause creates a new IEnumerable<T>
that wraps the IEnumerable<T>
returned by IteratorBlock
but this enumerable has yet to be enumerated. This happens when you execute a foreach
loop:
此时迭代器尚未执行。 Where子句创建一个新的IEnumerable
foreach (var evenNumber in evenNumbers)
Console.WriteLine(eventNumber);
If you enumerate the enumerable twice then a new instance of the state machine is created each time and your iterator block will execute the same code twice.
如果枚举可枚举的两次,则每次都会创建一个新的状态机实例,并且迭代器块将执行两次相同的代码。
Notice that LINQ methods like ToList()
, ToArray()
, First()
, Count()
etc. will use a foreach
loop to enumerate the enumerable. For instance ToList()
will enumerate all elements of the enumerable and store them in a list. You can now access the list to get all elements of the enumerable without the iterator block executing again. There is a trade-off between using CPU to produce the elements of the enumerable multiple times and memory to store the elements of the enumeration to access them multiple times when using methods like ToList()
.
请注意,像ToList(),ToArray(),First(),Count()等LINQ方法将使用foreach循环来枚举可枚举。例如,ToList()将枚举可枚举的所有元素并将它们存储在列表中。您现在可以访问列表以获取可枚举的所有元素,而无需再次执行迭代器块。在使用CPU生成多次可枚举元素和使用ToList()等方法存储枚举元素以多次访问它们之间需要进行权衡。
#11
10
The C# yield keyword, to put it simply, allows many calls to a body of code, referred to as an iterator, that knows how to return before it's done and, when called again, continues where it left off - i.e. it helps an iterator become transparently stateful per each item in a sequence that the iterator returns in successive calls.
简单地说,C#yield关键字允许对一个代码体(称为迭代器)的多次调用,它知道如何在它完成之前返回,并且当再次调用时,继续它停止的地方 - 即它有助于迭代器对迭代器在连续调用中返回的序列中的每个项目变为透明状态。
In JavaScript, the same concept is called Generators.
在JavaScript中,相同的概念称为生成器。
#12
6
It is a very simple and easy way to create an enumerable for your object. The compiler creates a class that wraps your method and that implements, in this case, IEnumerable<object>. Without the yield keyword, you'd have to create an object that implements IEnumerable<object>.
这是为对象创建可枚举的一种非常简单易用的方法。编译器创建一个包装您的方法的类,并在这种情况下实现IEnumerable
#13
6
If I understand this correctly, here's how I would phrase this from the perspective of the function implementing IEnumerable with yield.
如果我理解正确的话,这就是我如何从使用yield实现IEnumerable的函数的角度来说明这一点。
- Here's one.
- Call again if you need another.
- I'll remember what I already gave you.
- I'll only know if I can give you another when you call again.
如果您需要另一个,请再次致电
我会记得我已经给你的东西。
我再次打电话时,我才会知道是否能给你另一个。
#14
4
It's producing enumerable sequence. What it does is actually creating local IEnumerable sequence and returning it as a method result
它产生了可枚举的序列。它的作用实际上是创建本地IEnumerable序列并将其作为方法结果返回
#15
2
This link has a simple example
这个链接有一个简单的例子
Even simpler examples are here
这里有更简单的例子
public static IEnumerable<int> testYieldb()
{
for(int i=0;i<3;i++) yield return 4;
}
Notice that yield return won't return from the method. You can even put a WriteLine
after the yield return
请注意,yield return不会从方法返回。你甚至可以在收益率回报后放一个WriteLine
The above produces an IEnumerable of 4 ints 4,4,4,4
以上产生了4个int 4,4,4,4的IEnumerable
Here with a WriteLine
. Will add 4 to the list, print abc, then add 4 to the list, then complete the method and so really return from the method(once the method has completed, as would happen with a procedure without a return). But this would have a value, an IEnumerable
list of int
s, that it returns on completion.
这里有WriteLine。将列表添加4,打印abc,然后将4添加到列表中,然后完成方法,从而真正从方法返回(一旦方法完成,就像没有返回的过程一样)。但是这将有一个值,一个IEnumerable的int列表,它在完成时返回。
public static IEnumerable<int> testYieldb()
{
yield return 4;
console.WriteLine("abc");
yield return 4;
}
Notice also that when you use yield, what you are returning is not of the same type as the function. It's of the type of an element within the IEnumerable
list.
另请注意,当您使用yield时,返回的内容与函数的类型不同。它是IEnumerable列表中元素的类型。
You use yield with the method's return type as IEnumerable
. If the method's return type is int
or List<int>
and you use yield
, then it won't compile. You can use IEnumerable
method return type without yield but it seems maybe you can't use yield without IEnumerable
method return type.
您将yield与方法的返回类型一起用作IEnumerable。如果方法的返回类型是int或List
And to get it to execute you have to call it in a special way.
为了让它执行,你必须以特殊的方式调用它。
static void Main(string[] args)
{
testA();
Console.Write("try again. the above won't execute any of the function!\n");
foreach (var x in testA()) { }
Console.ReadLine();
}
// static List<int> testA()
static IEnumerable<int> testA()
{
Console.WriteLine("asdfa");
yield return 1;
Console.WriteLine("asdf");
}
#16
-2
It's trying to bring in some Ruby Goodness :)
Concept: This is some sample Ruby Code that prints out each element of the array
它试图引入一些Ruby Goodness :)概念:这是一些示例Ruby代码,它打印出数组的每个元素
rubyArray = [1,2,3,4,5,6,7,8,9,10]
rubyArray.each{|x|
puts x # do whatever with x
}
The Array's each method implementation yields control over to the caller (the 'puts x') with each element of the array neatly presented as x. The caller can then do whatever it needs to do with x.
Array的每个方法实现都会产生对调用者的控制('puts x'),数组的每个元素都整齐地呈现为x。然后,调用者可以执行x所需的任何操作。
However .Net doesn't go all the way here.. C# seems to have coupled yield with IEnumerable, in a way forcing you to write a foreach loop in the caller as seen in Mendelt's response. Little less elegant.
然而.Net并不是一直都在这里.C#似乎与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];
}
#1
609
The yield keyword actually does quite a lot here. The function returns an object that implements the IEnumerable interface. If a calling function starts foreach-ing over this object the function is called again until it "yields". This is syntactic sugar introduced in C# 2.0. In earlier versions you had to create your own IEnumerable and IEnumerator objects to do stuff like this.
yield关键字实际上在这里做了很多。该函数返回一个实现IEnumerable接口的对象。如果调用函数开始对该对象进行预处理,则再次调用该函数,直到它“产生”为止。这是C#2.0中引入的语法糖。在早期版本中,您必须创建自己的IEnumerable和IEnumerator对象来执行此类操作。
The easiest way understand code like this is to type in an example, set some breakpoints and see what happens.
理解这样的代码的最简单方法是输入一个示例,设置一些断点并查看会发生什么。
Try stepping through this for example:
尝试单步执行此操作,例如:
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;
}
When you step through the example you'll find the first call to Integers() returns 1. The second call returns 2 and the line "yield return 1" is not executed again.
当您单步执行该示例时,您将发现对Integers()的第一次调用返回1.第二次调用返回2并且不再执行“yield return 1”行。
Here is a real-life example
这是一个现实生活中的例子
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);
}
}
}
}
}
#2
334
Iteration. It creates a state machine "under the covers" that remembers where you were on each additional cycle of the function and picks up from there.
迭代。它创建了一个“在幕后”的状态机,它可以记住你在函数的每个附加周期中的位置并从中获取。
#3
163
Yield has two great uses,
产量有两大用途,
-
It helps to provide custom iteration without creating temp collections.
它有助于提供自定义迭代而无需创建临时集合。
-
It helps to do stateful iteration.
它有助于进行有状态迭代。
In order to explain above two points more demonstratively, I have created a simple video you can watch it here
为了更具说明性地解释上述两点,我创建了一个简单的视频,你可以在这里观看
#4
125
Recently Raymond Chen also ran an interesting series of articles on the yield keyword.
最近Raymond Chen还在yield关键字上发表了一系列有趣的文章。
- The implementation of iterators in C# and its consequences (part 1)
- The implementation of iterators in C# and its consequences (part 2)
- The implementation of iterators in C# and its consequences (part 3)
- The implementation of iterators in C# and its consequences (part 4)
C#中迭代器的实现及其后果(第1部分)
C#中迭代器的实现及其后果(第2部分)
C#中迭代器的实现及其后果(第3部分)
C#中迭代器的实现及其后果(第4部分)
While it's nominally used for easily implementing an iterator pattern, but can be generalized into a state machine. No point in quoting Raymond, the last part also links to other uses (but the example in Entin's blog is esp good, showing how to write async safe code).
虽然它名义上用于轻松实现迭代器模式,但可以推广到状态机。没有必要引用Raymond,最后一部分也链接到其他用途(但Entin的博客中的示例是esp好,显示如何编写异步安全代码)。
#5
53
At first sight, yield return is a .NET sugar to return an IEnumerable.
乍一看,yield return是一个返回IEnumerable的.NET糖。
Without yield, all the items of the collection are created at once:
如果没有产量,则立即创建集合的所有项目:
class SomeData
{
public SomeData() { }
static public IEnumerable<SomeData> CreateSomeDatas()
{
return new List<SomeData> {
new SomeData(),
new SomeData(),
new SomeData()
};
}
}
Same code using yield, it returns item by item:
使用yield的相同代码,它逐项返回:
class SomeData
{
public SomeData() { }
static public IEnumerable<SomeData> CreateSomeDatas()
{
yield return new SomeData();
yield return new SomeData();
yield return new SomeData();
}
}
The advantage of using yield is that if the function consuming your data simply needs the first item of the collection, the rest of the items won't be created.
使用yield的优点是,如果消耗数据的函数只需要集合的第一项,则不会创建其余项。
The yield operator allows the creation of items as it is demanded. That's a good reason to use it.
yield操作符允许根据需要创建项目。这是使用它的一个很好的理由。
#6
30
yield return
is used with enumerators. On each call of yield statement, control is returned to the caller but it ensures that the callee's state is maintained. Due to this, when the caller enumerates the next element, it continues execution in the callee method from statement immediately after the yield
statement.
yield return与枚举器一起使用。在每次调用yield语句时,控制权返回给调用者,但它确保维持被调用者的状态。因此,当调用者枚举下一个元素时,它会继续在yield语句之后的语句中的callee方法中执行。
Let us try to understand this with an example. In this example, corresponding to each line I have mentioned the order in which execution flows.
让我们试着通过一个例子来理解这一点。在这个例子中,对应于每一行,我提到了执行流程的顺序。
static void Main(string[] args)
{
foreach (int fib in Fibs(6))//1, 5
{
Console.WriteLine(fib + " ");//4, 10
}
}
static IEnumerable<int> Fibs(int fibCount)
{
for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
{
yield return prevFib;//3, 9
int newFib = prevFib + currFib;//6
prevFib = currFib;//7
currFib = newFib;//8
}
}
Also, the state is maintained for each enumeration. Suppose, I have another call to Fibs()
method then the state will be reset for it.
此外,每个枚举都保持状态。假设,我再次调用Fibs()方法,然后状态将被重置。
#7
29
Intuitively, the keyword returns a value from the function without leaving it, i.e. in your code example it returns the current item
value and then resumes the loop. More formally, it is used by the compiler to generate code for an iterator. Iterators are functions that return IEnumerable
objects. The MSDN has several articles about them.
直观地,关键字从函数返回一个值而不离开它,即在代码示例中它返回当前项值,然后恢复循环。更正式地说,编译器使用它来为迭代器生成代码。迭代器是返回IEnumerable对象的函数。 MSDN有几篇关于它们的文章。
#8
21
A list or array implementation loads all of the items immediately whereas the yield implementation provides a deferred execution solution.
列表或数组实现立即加载所有项,而yield实现提供延迟执行解决方案。
In practice, it is often desirable to perform the minimum amount of work as needed in order to reduce the resource consumption of an application.
在实践中,通常希望根据需要执行最少量的工作以减少应用程序的资源消耗。
For example, we may have an application that process millions of records from a database. The following benefits can be achieved when we use IEnumerable in a deferred execution pull-based model:
例如,我们可能有一个处理来自数据库的数百万条记录的应用程序。当我们在延迟执行基于拉的模型中使用IEnumerable时,可以实现以下好处:
- Scalability, reliability and predictability are likely to improve since the number of records does not significantly affect the application’s resource requirements.
- Performance and responsiveness are likely to improve since processing can start immediately instead of waiting for the entire collection to be loaded first.
- Recoverability and utilisation are likely to improve since the application can be stopped, started, interrupted or fail. Only the items in progress will be lost compared to pre-fetching all of the data where only using a portion of the results was actually used.
- Continuous processing is possible in environments where constant workload streams are added.
可扩展性,可靠性和可预测性可能会提高,因为记录数量不会显着影响应用程序的资源需求。
性能和响应性可能会提高,因为处理可以立即开始,而不是等待首先加载整个集合。
由于应用程序可以停止,启动,中断或失败,因此可恢复性和利用率可能会提高。与预取所有仅使用部分结果的数据相比,只有正在进行的项目将丢失。
在添加常量工作负载流的环境中可以进行连续处理。
Here is a comparison between build a collection first such as a list compared to using yield.
下面是构建集合的第一个例如列表与使用yield之间的比较。
List Example
public class ContactListStore : IStore<ContactModel>
{
public IEnumerable<ContactModel> GetEnumerator()
{
var contacts = new List<ContactModel>();
Console.WriteLine("ContactListStore: Creating contact 1");
contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
Console.WriteLine("ContactListStore: Creating contact 2");
contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
Console.WriteLine("ContactListStore: Creating contact 3");
contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
return contacts;
}
}
static void Main(string[] args)
{
var store = new ContactListStore();
var contacts = store.GetEnumerator();
Console.WriteLine("Ready to iterate through the collection.");
Console.ReadLine();
}
Console Output
ContactListStore: Creating contact 1
ContactListStore: Creating contact 2
ContactListStore: Creating contact 3
Ready to iterate through the collection.
控制台输出ContactListStore:创建联系人1 ContactListStore:创建联系人2 ContactListStore:创建联系人3准备迭代集合。
Note: The entire collection was loaded into memory without even asking for a single item in the list
注意:整个集合都已加载到内存中,甚至没有要求列表中的单个项目
Yield Example
public class ContactYieldStore : IStore<ContactModel>
{
public IEnumerable<ContactModel> GetEnumerator()
{
Console.WriteLine("ContactYieldStore: Creating contact 1");
yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
Console.WriteLine("ContactYieldStore: Creating contact 2");
yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
Console.WriteLine("ContactYieldStore: Creating contact 3");
yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
}
}
static void Main(string[] args)
{
var store = new ContactYieldStore();
var contacts = store.GetEnumerator();
Console.WriteLine("Ready to iterate through the collection.");
Console.ReadLine();
}
Console Output
Ready to iterate through the collection.
控制台输出准备迭代整个集合。
Note: The collection wasn't executed at all. This is due to the "deferred execution" nature of IEnumerable. Constructing an item will only occur when it is really required.
注意:集合根本没有执行。这是由于IEnumerable的“延迟执行”性质。只有在真正需要时才会构建项目。
Let's call the collection again and obverse the behaviour when we fetch the first contact in the collection.
让我们再次调用该集合,并在我们获取集合中的第一个联系人时讨论该行为。
static void Main(string[] args)
{
var store = new ContactYieldStore();
var contacts = store.GetEnumerator();
Console.WriteLine("Ready to iterate through the collection");
Console.WriteLine("Hello {0}", contacts.First().FirstName);
Console.ReadLine();
}
Console Output
Ready to iterate through the collection
ContactYieldStore: Creating contact 1
Hello Bob
控制台输出准备迭代集合ContactYieldStore:创建联系人1 Hello Bob
Nice! Only the first contact was constructed when the client "pulled" the item out of the collection.
太好了!当客户端将项目“拉出”集合时,仅构建了第一个联系人。
#9
14
Here is a simple way to understand the concept: The basic idea is, if you want a collection that you can use "foreach
" on, but gathering the items into the collection is expensive for some reason (like querying them out of a database), AND you will often not need the entire collection, then you create a function that builds the collection one item at a time and yields it back to the consumer (who can then terminate the collection effort early).
这是一个理解这个概念的简单方法:基本思想是,如果你想要一个可以使用“foreach”的集合,但是由于某些原因(比如从数据库中查询它们)将项目收集到集合中是昂贵的,并且您通常不需要整个集合,然后您创建一个函数,一次构建一个项目集合并将其返回给消费者(然后可以提前终止集合工作)。
Think of it this way: You go to the meat counter and want to buy a pound of sliced ham. The butcher takes a 10-pound ham to the back, puts it on the slicer machine, slices the whole thing, then brings the pile of slices back to you and measures out a pound of it. (OLD way). With yield
, the butcher brings the slicer machine to the counter, and starts slicing and "yielding" each slice onto the scale until it measures 1-pound, then wraps it for you and you're done. The Old Way may be better for the butcher (lets him organize his machinery the way he likes), but the New Way is clearly more efficient in most cases for the consumer.
可以这样想:你去肉类柜台,想要买一磅切好的火腿。屠夫把一个10磅重的火腿放在后面,把它放在切片机上,切成整片,然后将一堆切片带回给你,然后测出一磅。 (旧方式)。随着产量的提高,屠夫将切片机带到柜台,然后开始切片并“切割”每个切片到秤上,直到它测量到1磅,然后为你包装它就完成了。对于屠夫而言,旧方式可能更好(让他按照自己喜欢的方式组织他的机器),但对于消费者而言,新方式在大多数情况下显然更有效。
#10
11
The yield
keyword allows you to create an IEnumerable<T>
in the form on an iterator block. This iterator block supports deferred executing and if you are not familiar with the concept it may appear almost magical. However, at the end of the day it is just code that executes without any weird tricks.
yield关键字允许您在迭代器块上的表单中创建IEnumerable
An iterator block can be described as syntactic sugar where the compiler generates a state machine that keeps track of how far the enumeration of the enumerable has progressed. To enumerate an enumerable, you often use a foreach
loop. However, a foreach
loop is also syntactic sugar. So you are two abstractions removed from the real code which is why it initially might be hard to understand how it all works together.
迭代器块可以被描述为语法糖,其中编译器生成状态机,该状态机跟踪可枚举的枚举进展的程度。要枚举可枚举,您经常使用foreach循环。然而,foreach循环也是语法糖。因此,您从实际代码中删除了两个抽象,这就是为什么它最初可能很难理解它们如何一起工作。
Assume that you have a very simple iterator block:
假设您有一个非常简单的迭代器块:
IEnumerable<int> IteratorBlock()
{
Console.WriteLine("Begin");
yield return 1;
Console.WriteLine("After 1");
yield return 2;
Console.WriteLine("After 2");
yield return 42;
Console.WriteLine("End");
}
Real iterator blocks often have conditions and loops but when you check the conditions and unroll the loops they still end up as yield
statements interleaved with other code.
真正的迭代器块通常具有条件和循环,但是当您检查条件并展开循环时,它们仍然最终作为与其他代码交错的yield语句。
To enumerate the iterator block a foreach
loop is used:
要枚举迭代器块,使用foreach循环:
foreach (var i in IteratorBlock())
Console.WriteLine(i);
Here is the output (no surprises here):
这是输出(这里没有惊喜):
Begin 1 After 1 2 After 2 42 End
As stated above foreach
is syntactic sugar:
如上所述,foreach是句法糖:
IEnumerator<int> enumerator = null;
try
{
enumerator = IteratorBlock().GetEnumerator();
while (enumerator.MoveNext())
{
var i = enumerator.Current;
Console.WriteLine(i);
}
}
finally
{
enumerator?.Dispose();
}
In an attempt to untangle this I have crated a sequence diagram with the abstractions removed:
为了解开这个问题,我创建了一个删除了抽象的序列图:
The state machine generated by the compiler also implements the enumerator but to make the diagram more clear I have shown them as separate instances. (When the state machine is enumerated from another thread you do actually get separate instances but that detail is not important here.)
编译器生成的状态机也实现了枚举器,但为了使图更清晰,我将它们显示为单独的实例。 (当从另一个线程枚举状态机时,您实际上会获得单独的实例,但这里的详细信息并不重要。)
Every time you call your iterator block a new instance of the state machine is created. However, none of your code in the iterator block is executed until enumerator.MoveNext()
executes for the first time. This is how deferred executing works. Here is a (rather silly) example:
每次调用迭代器块时,都会创建一个新的状态机实例。但是,在第一次执行enumerator.MoveNext()之前,迭代器块中的所有代码都不会执行。这是延迟执行的工作原理。这是一个(相当愚蠢)的例子:
var evenNumbers = IteratorBlock().Where(i => i%2 == 0);
At this point the iterator has not executed. The Where
clause creates a new IEnumerable<T>
that wraps the IEnumerable<T>
returned by IteratorBlock
but this enumerable has yet to be enumerated. This happens when you execute a foreach
loop:
此时迭代器尚未执行。 Where子句创建一个新的IEnumerable
foreach (var evenNumber in evenNumbers)
Console.WriteLine(eventNumber);
If you enumerate the enumerable twice then a new instance of the state machine is created each time and your iterator block will execute the same code twice.
如果枚举可枚举的两次,则每次都会创建一个新的状态机实例,并且迭代器块将执行两次相同的代码。
Notice that LINQ methods like ToList()
, ToArray()
, First()
, Count()
etc. will use a foreach
loop to enumerate the enumerable. For instance ToList()
will enumerate all elements of the enumerable and store them in a list. You can now access the list to get all elements of the enumerable without the iterator block executing again. There is a trade-off between using CPU to produce the elements of the enumerable multiple times and memory to store the elements of the enumeration to access them multiple times when using methods like ToList()
.
请注意,像ToList(),ToArray(),First(),Count()等LINQ方法将使用foreach循环来枚举可枚举。例如,ToList()将枚举可枚举的所有元素并将它们存储在列表中。您现在可以访问列表以获取可枚举的所有元素,而无需再次执行迭代器块。在使用CPU生成多次可枚举元素和使用ToList()等方法存储枚举元素以多次访问它们之间需要进行权衡。
#11
10
The C# yield keyword, to put it simply, allows many calls to a body of code, referred to as an iterator, that knows how to return before it's done and, when called again, continues where it left off - i.e. it helps an iterator become transparently stateful per each item in a sequence that the iterator returns in successive calls.
简单地说,C#yield关键字允许对一个代码体(称为迭代器)的多次调用,它知道如何在它完成之前返回,并且当再次调用时,继续它停止的地方 - 即它有助于迭代器对迭代器在连续调用中返回的序列中的每个项目变为透明状态。
In JavaScript, the same concept is called Generators.
在JavaScript中,相同的概念称为生成器。
#12
6
It is a very simple and easy way to create an enumerable for your object. The compiler creates a class that wraps your method and that implements, in this case, IEnumerable<object>. Without the yield keyword, you'd have to create an object that implements IEnumerable<object>.
这是为对象创建可枚举的一种非常简单易用的方法。编译器创建一个包装您的方法的类,并在这种情况下实现IEnumerable
#13
6
If I understand this correctly, here's how I would phrase this from the perspective of the function implementing IEnumerable with yield.
如果我理解正确的话,这就是我如何从使用yield实现IEnumerable的函数的角度来说明这一点。
- Here's one.
- Call again if you need another.
- I'll remember what I already gave you.
- I'll only know if I can give you another when you call again.
如果您需要另一个,请再次致电
我会记得我已经给你的东西。
我再次打电话时,我才会知道是否能给你另一个。
#14
4
It's producing enumerable sequence. What it does is actually creating local IEnumerable sequence and returning it as a method result
它产生了可枚举的序列。它的作用实际上是创建本地IEnumerable序列并将其作为方法结果返回
#15
2
This link has a simple example
这个链接有一个简单的例子
Even simpler examples are here
这里有更简单的例子
public static IEnumerable<int> testYieldb()
{
for(int i=0;i<3;i++) yield return 4;
}
Notice that yield return won't return from the method. You can even put a WriteLine
after the yield return
请注意,yield return不会从方法返回。你甚至可以在收益率回报后放一个WriteLine
The above produces an IEnumerable of 4 ints 4,4,4,4
以上产生了4个int 4,4,4,4的IEnumerable
Here with a WriteLine
. Will add 4 to the list, print abc, then add 4 to the list, then complete the method and so really return from the method(once the method has completed, as would happen with a procedure without a return). But this would have a value, an IEnumerable
list of int
s, that it returns on completion.
这里有WriteLine。将列表添加4,打印abc,然后将4添加到列表中,然后完成方法,从而真正从方法返回(一旦方法完成,就像没有返回的过程一样)。但是这将有一个值,一个IEnumerable的int列表,它在完成时返回。
public static IEnumerable<int> testYieldb()
{
yield return 4;
console.WriteLine("abc");
yield return 4;
}
Notice also that when you use yield, what you are returning is not of the same type as the function. It's of the type of an element within the IEnumerable
list.
另请注意,当您使用yield时,返回的内容与函数的类型不同。它是IEnumerable列表中元素的类型。
You use yield with the method's return type as IEnumerable
. If the method's return type is int
or List<int>
and you use yield
, then it won't compile. You can use IEnumerable
method return type without yield but it seems maybe you can't use yield without IEnumerable
method return type.
您将yield与方法的返回类型一起用作IEnumerable。如果方法的返回类型是int或List
And to get it to execute you have to call it in a special way.
为了让它执行,你必须以特殊的方式调用它。
static void Main(string[] args)
{
testA();
Console.Write("try again. the above won't execute any of the function!\n");
foreach (var x in testA()) { }
Console.ReadLine();
}
// static List<int> testA()
static IEnumerable<int> testA()
{
Console.WriteLine("asdfa");
yield return 1;
Console.WriteLine("asdf");
}
#16
-2
It's trying to bring in some Ruby Goodness :)
Concept: This is some sample Ruby Code that prints out each element of the array
它试图引入一些Ruby Goodness :)概念:这是一些示例Ruby代码,它打印出数组的每个元素
rubyArray = [1,2,3,4,5,6,7,8,9,10]
rubyArray.each{|x|
puts x # do whatever with x
}
The Array's each method implementation yields control over to the caller (the 'puts x') with each element of the array neatly presented as x. The caller can then do whatever it needs to do with x.
Array的每个方法实现都会产生对调用者的控制('puts x'),数组的每个元素都整齐地呈现为x。然后,调用者可以执行x所需的任何操作。
However .Net doesn't go all the way here.. C# seems to have coupled yield with IEnumerable, in a way forcing you to write a foreach loop in the caller as seen in Mendelt's response. Little less elegant.
然而.Net并不是一直都在这里.C#似乎与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];
}