我经常发现使用yield return
语句返回IEnumerable的场景,然后有其他方法使用不同的参数调用此函数并直接返回结果。
与简单地返回集合相比,迭代结果yield return
是否有任何性能优势?即如果我在生成的IEnumberable上使用return
而不是再次循环遍历这些结果并使用yield return
,编译器知道只生成需要的结果,或者它是否等待整个集合在返回所有结果之前返回?
public class Demo
{
private IEnumerable<int> GetNumbers(int x)
{
//imagine this operation were more expensive than this demo version
//e.g. calling a web service on each iteration.
for (int i = 0; i < x; i++)
{
yield return i;
}
}
//does this wait for the full list before returning
public IEnumerable<int> GetNumbersWrapped()
{
return GetNumbers(10);
}
//whilst this allows the benefits of Yield Return to persist?
public IEnumerable<int> GetNumbersWrappedYield()
{
foreach (int i in GetNumbers(10))
yield return i;
}
}
答案 0 :(得分:3)
GetNumbersWrapped
只是传递原始的可枚举 - 它甚至没有在那时调用迭代器。最终结果仍然是完全延迟/延迟/假脱机/其他任何内容。
GetNumbersWrappedYield
添加了一个额外的抽象层 - 所以现在,每次调用MoveNext
都必须做更多更多的工作;不足以引起疼痛。它也将完全延迟/延迟/假脱机/其他任何东西 - 但增加了一些小的开销。通常这些小的开销是通过添加某些值(如过滤或其他处理)来证明的。但在这个例子中并不是真的。
注意:GetNumbersWrappedYield
做的一件事是防止来电者滥用。例如,如果GetNumbers
是:
private IEnumerable<int> GetNumbers(int x)
{
return myPrivateSecretList;
}
然后GetNumbersWrapped
的来电者可以滥用:
IList list = (IList)GetNumbers(4);
list.Clear();
list.Add(123); // mwahahaahahahah
然而,GetNumbersWrappedYield
的来电者不能这样做...至少,不是那么容易。当然,他们仍然可以使用反射将迭代器分开,获取包装的内部引用,并投射 。
然而,这通常不是真正的问题。
答案 1 :(得分:1)
返回IEnumberable<T>
的函数确实返回了一个对象,该对象不是完整列表,但知道在调用Enumerator
的{{1}}方法时如何迭代它。您的MoveNext
方法也是如此。它不等待完整的收集。它返回一个内部有GetNumbersWrapped
的对象。每当Enumerator
循环(或其他循环操作)调用此枚举器的foreach
方法时,它就会开始读取值。所以MoveNext
和GetNumbersWrapped
是相同的,只是GetNumbersWrappedYield
有一个冗余循环层。
答案 2 :(得分:1)
如果我正确理解了你的问题,函数GetNumbersWrapped
在返回之前不会等待完整列表,因为它的返回类型是IEnumerable,并且GetNumbers
内部循环的执行被推迟。 / p>
答案 3 :(得分:1)
您可能知道,IEnumerable<T>
会被懒惰地评估。这意味着所有三种方法都将有效地完全相同,但GetNumbersWrappedYield
将只包含在冗余枚举器中。这样做不会有任何性能上的好处,事实上,额外的枚举器会带来轻微的开销。我会直接坚持GetNumbers
。