yield关键字是c#中一直困扰我的关键字之一,我从来都不确定自己是否正确地使用了它。

在以下两段代码中,哪一段是首选的,为什么?

版本1:使用收益率

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;
        }
    }
}

版本2:返回列表

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

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

当前回答

我知道这是一个老问题,但我想提供一个例子,说明如何创造性地使用yield关键字。我真的从这个技巧中受益良多。希望这将有助于任何人谁偶然遇到这个问题。

注意:不要认为yield关键字仅仅是构建集合的另一种方式。收益率的很大一部分力量来自于这样一个事实,即在执行过程中暂停 方法或属性,直到调用代码遍历下一个值。以下是我的例子:

使用yield关键字(与Rob Eisenburg的Caliburn一起使用)。微协程实现)允许我像这样表达对web服务的异步调用:

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();
}

这将做的是打开我的BusyIndicator,在我的web服务上调用Login方法,将我的IsLoggedIn标志设置为返回值,然后关闭BusyIndicator。

Here's how this works: IResult has an Execute method and a Completed event. Caliburn.Micro grabs the IEnumerator from the call to HandleButtonClick() and passes it into a Coroutine.BeginExecute method. The BeginExecute method starts iterating through the IResults. When the first IResult is returned, execution is paused inside HandleButtonClick(), and BeginExecute() attaches an event handler to the Completed event and calls Execute(). IResult.Execute() can perform either a synchronous or an asynchronous task and fires the Completed event when it's done.

LoginResult看起来像这样:

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; }
}

它可能有助于设置类似这样的东西,并逐步执行以观察发生了什么。

希望这能帮助到一些人!我真的很喜欢探索不同的收益使用方式。

其他回答

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

使用版本2,在返回之前必须有完整的列表。 通过使用yield-return,您实际上只需要在返回前获得下一项。

除此之外,这有助于在更大的时间框架内分散复杂计算的计算成本。例如,如果列表连接到GUI,而用户从未访问到最后一页,则永远不会计算列表中的最终项。

yield-return更可取的另一种情况是IEnumerable表示无限集。考虑素数列表,或者无限随机数列表。您永远不能一次返回完整的IEnumerable,因此使用yield-return以增量方式返回列表。

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

Yield return对于需要遍历数百万个对象的算法来说非常强大。考虑以下示例,您需要计算可能的拼车行程。首先我们生成可能的行程:

    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
            }
        }
    }

如果您使用List而不是yield,您将需要为内存分配100万个对象(~190mb),而这个简单的示例将花费~1400ms运行。但是,如果使用yield,就不需要将所有这些临时对象都放到内存中,而且算法速度会大大加快:本例只需要大约400ms就可以运行,完全不消耗内存。

假设您的产品LINQ类使用类似的yield来枚举/迭代,第一个版本更有效,因为它每次迭代只产生一个值。

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

收益率有两大用途

它有助于在不创建临时集合的情况下提供自定义迭代。(加载所有数据并循环)

它有助于进行有状态迭代。(流)

下面是一个简单的视频,我已经创建了完整的演示,以支持上述两点

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

这有点离题了,但由于这个问题被标记为最佳实践,我将继续发表我的意见。对于这种类型的东西,我非常喜欢把它变成一个属性:

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

            return products;
        }
    }
}

当然,它更像一个样板,但使用它的代码将看起来更干净:

prices = Whatever.AllProducts.Select (product => product.price);

vs

prices = Whatever.GetAllProducts().Select (product => product.price);

注意:我不会对任何可能需要一段时间才能完成工作的方法这样做。