我已经看到了正在使用相当多的堆栈溢出和博客的产量关键字。 我不使用LINQ 。 有人可以解释yield关键字?
我知道,类似的问题存在。 但没有真正解释什么是它的朴素简单的语言使用。
我已经看到了正在使用相当多的堆栈溢出和博客的产量关键字。 我不使用LINQ 。 有人可以解释yield关键字?
我知道,类似的问题存在。 但没有真正解释什么是它的朴素简单的语言使用。
到目前为止,这方面最好的解释(我见过)是乔恩斯基特的书 - 这章是免费的! 第6章, C#中的深度 。 没有什么我可以在这里补充一点,不是盖的。
然后买的书; 你将是一个更好的C#程序员。
问:我为什么不写一个较长的答案在这里(从意见转述); 简单。 作为埃里克利珀观察( 这里 ),在yield
结构(以及去其背后的魔法) 是在C#编译器代码的最复杂的位 ,并尝试和描述它在一份简短的答复这里是天真的最好的。 有这么多的细微差别来yield
,海事组织最好是指预先存在的(和完全合格的)资源。
Eric的博客现在有7项(这还只是近期的)讨论yield
。 我对埃里克·尊重了大量的 ,但他的博客可能更适合作为“更多信息”的人谁是舒适的主题( yield
在这种情况下),因为它通常描述了很多的背景设计考虑。 在合理的基础的情况下最好的做法。
(是的,第6章不下载,我验证了...)
的yield
关键字用于与返回方法IEnumerable<T>
或IEnumerator<T>
它使编译器生成实现必需的管道使用迭代器的类。 例如
public IEnumerator<int> SequenceOfOneToThree() {
yield return 1;
yield return 2;
yield return 3;
}
鉴于上述编译器的会产生一个实现类IEnumerator<int>
, IEnumerable<int>
和IDisposable
(实际上它也将实现的非通用版本IEnumerable
和IEnumerator
)。
这使您可以调用该方法SequenceOfOneToThree
中foreach
循环这样
foreach(var number in SequenceOfOneToThree) {
Console.WriteLine(number);
}
迭代器是一个状态机,所以每次yield
被称为在该方法中的位置被记录。 如果迭代被移动到下一个元素,该方法这个位置之后重新开始。 因此,在第一次迭代返回1,标志着该位置。 接下来的迭代器恢复后正确的,因而返回2等等。
不用说,你可以生成你喜欢的任何方式的顺序,这样你就不必硬编码的数字,像我一样。 另外,如果你想打破循环,可以使用yield break
。
在努力神秘性,我会避免谈论迭代器,因为他们可能是谜本身的一部分。
收益率回报和产量break语句是最经常被用来提供集合的“延迟评估”。
这意味着,当你得到一个使用产回报的事情,你试图让不存在共同的收集又一个方法的值(它本质上是空的)。 当你遍历他们(使用的foreach),将执行当时的方法,并获得枚举中的下一个元素。
某些属性和方法将导致整个枚举一次进行评估(如“计数”)。
下面是返回集合并返回产量之间的差异的一个简单的例子:
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.");
该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;
}
}
}
看一看在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);
}
}
}
埃里克·怀特在函数式编程系列非常值得读在它的全部,但对产量的条目是明确的,因为我已经看到了一个解释。
yield
是没有直接关系的LINQ,而是迭代器块 。 链接的MSDN 文章给出了这种语言的功能非常详细。 特别参见使用迭代器部分。 对于迭代器块的深详情,请参阅埃里克利珀最近的博客帖子上的功能。 对于一般的概念,请参阅Wikipedia 文章上的迭代器。
我想出了这个克服不必手动深拷贝名单一个.NET缺点。
我用这个:
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做这个工作,但它是不可能的,因为产生匿名方法块内部不能正常工作。
编辑:
更妙的是,使用通用名单拷贝机:
class Utility<T> where T : ICloneable
{
static public IEnumerable<T> CloneList(List<T> tl)
{
foreach (T t in tl)
{
yield return (T)t.Clone();
}
}
}
让我加入到这一切。 产率不是关键字。 如果使用“回报收益率”以外,它会像一个正常的变量等它才会工作。
它是用来从一个函数返回迭代器。 您可以进一步对搜索。 我建议您搜索“返回数组VS迭代器”