我已经看到在Stack Overflow和博客上使用了yield关键字。我不使用LINQ。有人可以解释yield关键字吗?
我知道存在类似的问题。 但是没有一个能用简单的语言解释它的用法。
答案 0 :(得分:32)
到目前为止,对此(我见过)的最佳解释是Jon Skeet的书 - 那一章是免费的!第6章,C# in Depth。我无法在这里添加任何未涵盖的内容。
然后买书;你将成为一名更好的C#程序员。
yield
构造(及其背后的魔力)是C#编译器中单一最复杂的代码,并且试着在一个简短的回复中描述它在这里是天真的。 yield
有很多细微差别,IMO最好引用预先存在的(并且完全合格的)资源。
Eric的博客现在有7个条目(这只是最近的条目)讨论yield
。我对Eric有很强烈的尊重,但他的博客可能更适合作为 对这个主题感到满意的人的“进一步信息”({{1}在这种情况下),因为它通常描述了许多背景设计注意事项。最好在合理的基础上完成。
(是的,第6章 下载;我验证了......)
答案 1 :(得分:30)
yield
关键字与返回IEnumerable<T>
或IEnumerator<T>
的方法一起使用,它使编译器生成一个类,该类实现使用迭代器所需的管道。例如。
public IEnumerator<int> SequenceOfOneToThree() {
yield return 1;
yield return 2;
yield return 3;
}
鉴于上述情况,编译器将生成一个实现IEnumerator<int>
,IEnumerable<int>
和IDisposable
的类(实际上它还将实现IEnumerable
的非泛型版本和{ {1}})。
这允许您在IEnumerator
循环中调用方法SequenceOfOneToThree
,如下所示
foreach
迭代器是状态机,因此每次调用foreach(var number in SequenceOfOneToThree) {
Console.WriteLine(number);
}
时,都会记录方法中的位置。如果迭代器移动到下一个元素,则该方法在此位置后立即恢复。所以第一次迭代返回1并标记该位置。下一个迭代器在一个之后重新开始,因此返回2,依此类推。
毋庸置疑,您可以以任何您喜欢的方式生成序列,因此您不必像我那样对数字进行硬编码。此外,如果您想要打破循环,可以使用yield
。
答案 2 :(得分:18)
为了揭开神秘面孔,我会避免谈论迭代器,因为它们本身可能成为神秘的一部分。
收益率收益率和收益率中断报表通常用于提供收集的“延期评估”。
这意味着当你获得使用yield return的方法的值时,你想要获得的东西的集合还不存在(它本质上是空的)。当你遍历它们时(使用foreach),它将在那时执行方法并获取枚举中的下一个元素。
某些属性和方法将导致立即计算整个枚举(例如“Count”)。
以下是返回集合和返回收益率之间差异的快速示例:
string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" };
public IEnumerable<string> GetYieldEnumerable()
{
foreach (var name in names)
yield return name;
}
public IEnumerable<string> GetList()
{
var list = new List<string>();
foreach (var name in names)
list.Add(name);
return list;
}
// we're going to execute the GetYieldEnumerable() method
// but the foreach statement inside it isn't going to execute
var yieldNames = GetNamesEnumerable();
// now we're going to execute the GetList() method and
// the foreach method will execute
var listNames = GetList();
// now we want to look for a specific name in yieldNames.
// only the first two iterations of the foreach loop in the
// GetYieldEnumeration() method will need to be called to find it.
if (yieldNames.Contains("Jim")
Console.WriteLine("Found Jim and only had to loop twice!");
// now we'll look for a specific name in listNames.
// the entire names collection was already iterated over
// so we've already paid the initial cost of looping through that collection.
// now we're going to have to add two more loops to find it in the listNames
// collection.
if (listNames.Contains("Jim"))
Console.WriteLine("Found Jim and had to loop 7 times! (5 for names and 2 for listNames)");
如果您需要在源数据具有值之前获取对枚举的引用,也可以使用此方法。例如,如果名称集合未完成,则开头:
string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" };
public IEnumerable<string> GetYieldEnumerable()
{
foreach (var name in names)
yield return name;
}
public IEnumerable<string> GetList()
{
var list = new List<string>();
foreach (var name in names)
list.Add(name);
return list;
}
var yieldNames = GetNamesEnumerable();
var listNames = GetList();
// now we'll change the source data by renaming "Jim" to "Jimbo"
names[1] = "Jimbo";
if (yieldNames.Contains("Jimbo")
Console.WriteLine("Found Jimbo!");
// Because this enumeration was evaluated completely before we changed "Jim"
// to "Jimbo" it isn't going to be found
if (listNames.Contains("Jimbo"))
// this can't be true
else
Console.WriteLine("Couldn't find Jimbo, because he wasn't there when I was evaluated.");
答案 3 :(得分:10)
yield
关键字是撰写IEnumerator
的便捷方式。例如:
public static IEnumerator<int> Range(int from, int to)
{
for (int i = from; i < to; i++)
{
yield return i;
}
}
由C#编译器转换为类似于:
的东西public static IEnumerator<int> Range(int from, int to)
{
return new RangeEnumerator(from, to);
}
class RangeEnumerator : IEnumerator<int>
{
private int from, to, current;
public RangeEnumerator(int from, int to)
{
this.from = from;
this.to = to;
this.current = from;
}
public bool MoveNext()
{
this.current++;
return this.current < this.to;
}
public int Current
{
get
{
return this.current;
}
}
}
答案 4 :(得分:6)
查看MSDN文档和示例。它本质上是一种在C#中创建迭代器的简单方法。
public class List
{
//using System.Collections;
public static IEnumerable Power(int number, int exponent)
{
int counter = 0;
int result = 1;
while (counter++ < exponent)
{
result = result * number;
yield return result;
}
}
static void Main()
{
// Display powers of 2 up to the exponent 8:
foreach (int i in Power(2, 8))
{
Console.Write("{0} ", i);
}
}
}
答案 5 :(得分:4)
答案 6 :(得分:3)
yield
与LINQ没有直接关系,而是与iterator blocks直接相关。关联的MSDN article提供了有关此语言功能的详细信息。请参阅Using Iterators部分。有关迭代器块的详细信息,请参阅Eric Lippert最近关于该功能的博客posts。有关一般概念,请参阅有关迭代器的Wikipedia article。
答案 7 :(得分:2)
我提出这个来克服.NET必须手动深度复制List的缺点。
我用这个:
static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
foreach (SpotPlacement sp in spotPlacements)
{
yield return (SpotPlacement)sp.Clone();
}
}
在另一个地方:
public object Clone()
{
OrderItem newOrderItem = new OrderItem();
...
newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
...
return newOrderItem;
}
我试图找到执行此操作的oneliner,但由于无法在匿名方法块中工作,因此无法实现。
编辑:
更好的是,使用通用的List cloner:
class Utility<T> where T : ICloneable
{
static public IEnumerable<T> CloneList(List<T> tl)
{
foreach (T t in tl)
{
yield return (T)t.Clone();
}
}
}
答案 8 :(得分:0)
让我补充一切。收益率不是关键字。 它只有在你使用“yield return”时才有效,除了它将像普通变量一样工作。
用于从函数返回迭代器。您可以进一步搜索。 我建议搜索“返回阵列与迭代器”