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 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就可以运行,完全不消耗内存。

其他回答

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

这个问题没有对错之分。哪一种更可取取决于具体情况。例如,如果您必须在一定时间内完成查询,并且需要对结果执行一些半复杂的操作,那么第二个版本可能更可取。但是要注意大的结果集,特别是在以32位模式运行这段代码时。在使用这个方法时,我已经被OutOfMemory异常咬了好几次。

要记住的关键是:区别在于效率。因此,您可能应该使用任何使代码更简单的方法,并且只在分析之后更改它。

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

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

直接返回列表。好处:

这样就更清楚了 该列表是可重用的。(迭代器不是)不是真的,谢谢Jon

You should use the iterator (yield) from when you think you probably won't have to iterate all the way to the end of the list, or when it has no end. For example, the client calling is going to be searching for the first product that satisfies some predicate, you might consider using the iterator, although that's a contrived example, and there are probably better ways to accomplish it. Basically, if you know in advance that the whole list will need to be calculated, just do it up front. If you think that it won't, then consider using the iterator version.

作为理解何时应该使用yield的概念性示例,假设方法ConsumeLoop()处理ProduceList()返回/产生的项:

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

IEnumerable<Consumable> ProduceList() {
    while (KeepProducing())
        yield return ProduceExpensiveConsumable();    // expensive
}

如果没有yield,对ProduceList()的调用可能需要很长时间,因为你必须在返回之前完成列表:

//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]

使用yield,它会重新排列,有点交错:

//pseudo-assembly
Produce consumable[0]
Consume consumable[0]                   // immediately yield & Consume
Produce consumable[1]                   // ConsumeLoop iterates, requesting next item
Consume consumable[1]                   // consume next
Produce consumable[2]
Consume consumable[2]                   // consume next
Produce consumable[3]
Consume consumable[3]                   // consume next

最后,正如之前许多人已经建议的那样,您应该使用版本2,因为您已经有了完整的列表。

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

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

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