屈服 关键字是其中之一 关键词 在 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。

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

除此之外,这有助于在更大的时间范围内分散复杂计算的计算成本。例如,如果列表连接到GUI并且用户永远不会转到最后一页,则永远不会计算列表中的最终项目。

另一种情况,其中yield-return是优选的,如果IEnumerable表示无限集。考虑素数列表,或无限的随机数列表。你永远不能一次返回完整的IEnumerable,所以你使用yield-return来逐步返回列表。

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

其他提示

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

作为理解何时应该使用 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 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

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

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

我知道这是一个老问题,但我想提供一个示例来说明如何创造性地使用 Yield 关键字。我有 真的 受益于这项技术。希望这对偶然发现这个问题的其他人有所帮助。

笔记:不要将yield 关键字仅仅视为构建集合的另一种方式。产量的力量很大一部分来自于执行力 暂停 在您的方法或属性中,直到调用代码在下一个值上迭代为止。这是我的例子:

使用yield关键字(与Rob Eisenburg的 Caliburn.Micro 协程 实现)允许我表达对 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();
}

其作用是打开 BusyIndi​​cator,调用 Web 服务上的 Login 方法,将 IsLoggedIn 标志设置为返回值,然后关闭 BusyIndi​​cator。

这是它的工作原理:IResult 有一个 Execute 方法和一个 Completed 事件。Caliburn.Micro 从对 HandleButtonClick() 的调用中获取 IEnumerator,并将其传递给 Coroutine.BeginExecute 方法。BeginExecute 方法开始迭代 IResults。当返回第一个 IResult 时,执行会在 HandleButtonClick() 内暂停,BeginExecute() 将事件处理程序附加到 Completed 事件并调用 Execute()。IResult.Execute() 可以执行同步或异步任务,并在完成时触发 Completed 事件。

登录结果看起来像这样:

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非常强大。请考虑以下示例,您需要计算rideshare的可能行程。首先,我们产生可能的旅行:

    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(trips))
        {
            // possible trip is actually calculated only at this point, because of yield
            if (IsTripGood(trip))
            {
                // match good trip
            }
        }
    }

如果使用List而不是yield,则需要将100万个对象分配给内存(~190mb),这个简单的例子需要大约1400ms才能运行。但是,如果使用yield,则不需要将所有这些临时对象放入内存中,并且您将获得明显更快的算法速度:此示例将仅运行约400毫秒而根本没有内存消耗。

这两段代码实际上做了两件不同的事情。第一个版本将根据您的需要拉取成员。第二个版本会将所有结果加载到内存开始对它做任何事情。

这个答案没有正确或错误的答案。哪一个更可取仅取决于具体情况。例如,如果您必须完成查询的时间有限,并且您需要执行与结果半复杂的操作,则第二个版本可能更可取。但要注意大型结果集,特别是如果您在32位模式下运行此代码。在执行此方法时,我多次被OutOfMemory异常所困扰。

要记住的关键是:差异在于效率。因此,您可能应该选择使代码更简单的方法,并在分析后更改它。

Yield有两个很好的用途

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

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

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

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

这是 Chris Sells C#编程语言;

  

我有时会忘记收益率与收益率不一样   可以执行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!
}
     

在这些情况下,记住收益率回报不是&#8220;最终&#8221;喜欢   返回很有帮助。

假设你的产品LINQ类使用类似的枚举/迭代产量,第一个版本效率更高,因为它每次迭代时只产生一个值。

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

除了这一点之外,这有点类似,但由于这个问题被标记为最佳实践,我将继续并投入我的两分钱。对于这种类型的东西,我更喜欢把它变成一个属性:

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

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

那怎么样?

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

        return products.ToList();
    }
}

我想这更清洁了。不过,我手边没有VS2008。 在任何情况下,如果Products实现IEnumerable(似乎 - 它在foreach语句中使用),我会直接返回它。

在这种情况下,我会使用代码的第2版。由于您拥有可用产品的完整列表,而这正是“消费者”所期望的产品。对于此方法调用,将需要将完整信息发送回调用方。

如果此方法的调用者需要“一个”,一次信息和下一个信息的消费是按需的,那么使用yield return将是有益的,这将确保当一个信息单元可用时,执行命令将返回给调用者。

可以使用收益率回报的一些例子是:

  1. 复杂的逐步计算,其中调用者一次等待一个步骤的数据
  2. 在GUI中进行分页 - 用户可能永远不会到达最后一页,只需要在当前页面上公开信息子集
  3. 要回答你的问题,我会使用版本2.

直接返回列表。好处:

  • 更清楚了
  • 该列表是可重复使用的。(迭代器不是) 实际上并非如此,谢谢乔恩

当您认为可能不必一直迭代到列表末尾或列表没有结尾时,您应该使用迭代器(yield)。例如,客户端调用将搜索满足某些谓词的第一个产品,您可能会考虑使用迭代器,尽管这是一个人为的示例,并且可能有更好的方法来完成它。基本上,如果您提前知道需要计算整个列表,则只需预先执行即可。如果您认为不会,请考虑使用迭代器版本。

yield return keyphrase用于维护特定集合的状态机。只要CLR看到正在使用的yield return keyphrase,CLR就会对该段代码实现Enumerator模式。这种类型的实现有助于开发人员处理所有类型的管道,否则我们将缺少关键字。

假设开发人员正在过滤某个集合,通过集合进行迭代,然后在某个新集合中提取这些对象。这种管道非常单调。

有关此处的关键字的详情文章

yield 的使用类似于关键字 return ,但它会返回 生成器 生成器对象只会遍历一次

产量有两个好处:

  1. 您不需要两次读取这些值;
  2. 您可以获得许多子节点,但不必将它们全部放在内存中。
  3. 还有另一个明确的解释也许可以帮到你。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top