“Yield Return”背后的奥秘






4.48/5 (21投票s)
如果你曾经使用过集合,那么你可能遇到过 yield return,但是除非你亲自尝试,否则你可能永远不会知道它是一种非常独特的方式来处理你的集合。这篇文章讨论了在你的程序中使用 yield return。
如果你已经使用 .NET 编码一段时间了,那么你很可能遇到过一个返回 IEnumerable<T>
的方法。如果你和我一样,你会意识到这会返回一个你可以循环和评估的项列表。如果你深入研究过代码,你可能会发现一个有趣的 return
语句 yield return
。
它基本上就是一个数组,对吧?我们只是将每个值作为一个集合返回以进行循环,不是吗? 好吧,这已经不是第一次某人(比如我自己)误解了它的目的,但实际上是有区别的。
让我们看一个返回集合的相当常见的例子。
常见的嫌疑人
private string[] _Names = { "Joe", "Mary", "Bob" };
//a collection of names using a string array
public string[] GetResultsAsArray() {
List<string> results = new List<string>();
foreach (string name in this._Names) {
Console.WriteLine("In GetResultsAsArray() : {0}", name);
results.Add(name);
}
return results.ToArray();
}
//a collection of names using IEnumerable<string>
public IEnumerable<string> GetResultsAsEnumerable() {
foreach (string name in this._Names) {
Console.WriteLine("In GetResultsAsEnumerable() : {0}", name);
yield return name;
}
}
这是两个常见的例子。第一个类似于 StackOverflow
上那个小丑在上面链接中说的那样。第二个使用 yield return
返回结果。
因此,如果我们将这些值分配给一个变量,我们的 Console
会读取什么?
var array = GetResultsAsArray();
var enumerable = GetResultsAsEnumerable();
Console.ReadKey();
In GetResultsAsArray() : Joe
In GetResultsAsArray() : Mary
In GetResultsAsArray() : Bob
现在,我第一次遇到这种情况时,我很震惊 - 我知道我分配了我的结果 - 发生了什么?
事实证明,这里有一个叫做延迟计算的小东西 - 除非我们需要它,否则该方法不会被调用。而且由于我们没有在任何地方使用我们的结果,所以该方法实际上永远不会被执行。
懒惰而又渴望
因此,我们已经确定,除非我们使用我们方法的结果,否则该方法永远不会被执行,这实际上是一个非常方便的功能。
现在,这里还有一个问题给你,当我们确实使用我们的结果时 - 会发生什么? 考虑一下这段代码。
Console.WriteLine("Array -- ");
foreach (string result in GetResultsAsArray()) {
Console.WriteLine("In the array loop : " + result);
}
Console.WriteLine("\nEnumerable -- ");
foreach (string result in GetResultsAsEnumerable()) {
Console.WriteLine("In the enumerable loop : " + result);
}
Console.ReadKey();
Array –
In GetResultsAsArray() : Joe
In GetResultsAsArray() : Mary
In GetResultsAsArray() : Bob
In the array loop : Joe
In the array loop : Mary
In the array loop : Bob
Enumerable –
In GetResultsAsEnumerable() : Joe
In the enumerable loop : Joe
In GetResultsAsEnumerable() : Mary
In the enumerable loop : Mary
In GetResultsAsEnumerable() : Bob
In the enumerable loop : Bob
每次 yield return
将一个值发送回我们的循环时,它都会立即被评估!对于懒惰的代码来说,这肯定很快就能给我们答案!
懒惰 - 并且可能迟到
这是一个懒惰会让你陷入麻烦的例子,特别是如果你不清楚它做什么。
IEnumerable<SearchResult> results;
using (SearchEngine search = new SearchEngine()) {
results = search.GetEnumerableSearchResults();
}
foreach(SearchResult item in results) {
//KABOOM!!
}
这段代码有什么问题? 我会告诉你 - 这是对 GetEnumerableSearchResults()
的那个懒惰的,一无是处的调用,就是它!
由于我们的代码在我们已经处理了我们的对象之后才被评估,所以当该方法最终被调用时,那里什么都没有! 看起来有人启动得太晚了。
当然,像这样的代码有很多实际用途,所以只要确保你正确使用它,你就会发现它是你编程工具包中的一个宝贵补充。
不要相信任何人... 除了你的母亲*
所以事实证明,两者之间确实存在差异,并且 不要让 StackOverflow 上的任何那些傻瓜告诉你其他情况,特别是当那个傻瓜是我的时候!