正确使用 “收益率”

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        foreach (Product product in products)
        {
            yield return product;
        }
    }
}
public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList<Product>();
    }
}

答案

当我计算列表中的下一个项目(甚至下一个项目组)时,我倾向于使用 yield-return。

使用版本 2 之前,您必须具有完整的列表,然后才能返回。通过使用 yield-return,您实际上只需要在退货之前就拥有下一项。

除其他事项外,这有助于在较长的时间范围内分散复杂计算的计算成本。例如,如果列表已连接到 GUI,并且用户从不进入最后一页,则您将永远不会计算列表中的最终项目。

如果 IEnumerable 表示一个无限集,则最好返回 yield 的另一种情况。考虑素数列表,或无限数量的随机数。您永远无法一次返回完整的 IEnumerable,因此您可以使用 yield-return 递增地返回列表。

在您的特定示例中,您拥有完整的产品列表,因此我将使用版本 2。

填充临时列表就像下载整个视频,而使用yield就像流传输该视频。

void ConsumeLoop() {
    foreach (Consumable item in ProduceList())        // might have to wait here
        item.Consume();
}

IEnumerable<Consumable> ProduceList() {
    while (KeepProducing())
        yield return ProduceExpensiveConsumable();    // expensive
}
//pseudo-assembly
Produce consumable[0]                   // expensive operation, e.g. disk I/O
Produce consumable[1]                   // waiting...
Produce consumable[2]                   // waiting...
Produce consumable[3]                   // completed the consumable list
Consume consumable[0]                   // start consuming
Consume consumable[1]
Consume consumable[2]
Consume consumable[3]
//pseudo-assembly
Produce consumable[0]
Consume consumable[0]                   // immediately Consume
Produce consumable[1]
Consume consumable[1]                   // consume next
Produce consumable[2]
Consume consumable[2]                   // consume next
Produce consumable[3]
Consume consumable[3]                   // consume next
public IEnumerable<IResult> HandleButtonClick() {
    yield return Show.Busy();

    var loginCall = new LoginResult(wsClient, Username, Password);
    yield return loginCall;
    this.IsLoggedIn = loginCall.Success;

    yield return Show.NotBusy();
}
public LoginResult : IResult {
    // Constructor to set private members...

    public void Execute(ActionExecutionContext context) {
        wsClient.LoginCompleted += (sender, e) => {
            this.Success = e.Result;
            Completed(this, new ResultCompletionEventArgs());
        };
        wsClient.Login(username, password);
    }

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
    public bool Success { get; private set; }
}

这似乎是一个奇怪的建议,但是我通过阅读有关 Python 生成器的演示文稿来学习如何在 C#中使用yield关键字:David M. Beazley 的http://www.dabeaz.com/generators/Generators.pdf 。您不需要了解太多 Python 就可以了解演示文稿 - 我不是。我发现它不仅有助于解释发电机的工作原理,而且对于您为什么要关心它很有帮助。

static IEnumerable<Trip> CreatePossibleTrips()
    {
        for (int i = 0; i < 1000000; i++)
        {
            yield return new Trip
            {
                Id = i.ToString(),
                Driver = new Driver { Id = i.ToString() }
            };
        }
    }
static void Main(string[] args)
    {
        foreach (var trip in CreatePossibleTrips())
        {
            // possible trip is actually calculated only at this point, because of yield
            if (IsTripGood(trip))
            {
                // match good trip
            }
        }
    }

这两段代码实际上在做两件事。第一个版本将根据需要拉成员。第二个版本将开始执行任何操作之前将所有结果加载到内存中。

这个答案没有对与错。哪种情况更好取决于具体情况。例如,如果您有一定的时间限制来完成查询,并且您需要对结果进行一些半复杂的操作,那么第二个版本可能会更好。但是要当心大型结果集,尤其是如果您以 32 位模式运行此代码时。执行此方法时,我多次被 OutOfMemory 异常咬伤。

但是要记住的关键是:差异在于效率。因此,您可能应该选择使您的代码更简单的任何一种,并仅在进行概要分析后才对其进行更改。

产量有两个重大用途

它有助于提供自定义迭代,而无需创建临时集合。 (加载所有数据并循环)

它有助于进行有状态的迭代。 (流式传输)

以下是我制作的简单视频,并进行了全面演示,以支持上述两点

http://www.youtube.com/watch?v=4fju3xcm21M

克里斯 • 塞尔斯(Chris Sells) 用 C#编程语言讲述了这些陈述;

有时我会忘记 yield return 与 return 不同,因为 yield return 之后的代码可以执行。例如,第一次返回后的代码将永远无法执行:

int F() {
return 1;
return 2; // Can never be executed
}

相反,可以在这里执行第一个收益率返回之后的代码:

IEnumerable<int> F() {
yield return 1;
yield return 2; // Can be executed
}

这经常在 if 语句中给我带来痛苦:

IEnumerable<int> F() {
if(...) { yield return 1; } // I mean this to be the only
// thing returned
yield return 2; // Oops!
}

在这些情况下,记住收益率并不是像收益率那样的 “最终” 收益。

假设您的产品 LINQ 类使用类似的产量进行枚举 / 迭代,则第一个版本的效率更高,因为它每次迭代都仅产生一个值。

第二个示例是使用 ToList()方法将枚举器 / 迭代器转换为列表。这意味着它将手动遍历枚举器中的所有项目,然后返回一个平面列表。