65.9K
CodeProject 正在变化。 阅读更多。
Home

“Yield Return”背后的奥秘

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.48/5 (21投票s)

2009 年 7 月 14 日

CC (ASA 2.5)

2分钟阅读

viewsIcon

48732

如果你曾经使用过集合,那么你可能遇到过 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 上的任何那些傻瓜告诉你其他情况,特别是当那个傻瓜是我的时候!

*我意大利祖父过去常说的一句话。

© . All rights reserved.