【问题标题】:Method not called when using yield return使用 yield return 时未调用方法
【发布时间】:2011-02-08 19:22:33
【问题描述】:

我在使用 yield return 的方法时遇到了一点问题,这不起作用...

public IEnumerable<MyClass> SomeMethod(int aParam)
{
    foreach(DataRow row in GetClassesFromDB(aParam).Rows)
    {
        yield return new MyClass((int)row["Id"], (string)row["SomeString"]);
    }    
}

上面的代码永远不会运行,当调用这个方法时它只是跳过它。

但是,如果我更改为...

public IEnumerable<MyClass> SomeMethod(int aParam)
{
    IList<MyClass> classes = new List<MyClass>();

    foreach(DataRow row in GetClassesFromDB(aParam).Rows)
    {
         classes.Add(new MyClass((int)rows["Id"], (string)row["SomeString"]);
    }

    return classes;
}

效果很好。

我不明白为什么第一种方法永远不会运行,你能帮我理解这里发生了什么吗?

【问题讨论】:

  • 像这样调用构造函数:Prop = SomeMethod(param);

标签: c#-2.0 yield-return


【解决方案1】:

当我决定让我们公司的解析器懒惰地读取传入数据时,我不得不以一种近乎灾难性的方式了解yield 是多么酷/多么危险。幸运的是,我们的少数实现函数中只有一个实际使用了 yield 关键字。花了几天时间才意识到它根本没有做任何工作。

yield 关键字将尽可能地懒惰,包括完全跳过该方法,如果您不将其与 .ToList().FirstOrDefault().Any() 之类的东西一起使用的话。

以下是两种变体,一种使用关键字,另一种返回直接列表。一个甚至不会费心去执行,而另一个会,即使它们看起来一样。

public class WhatDoesYieldDo
{
    public List<string> YieldTestResults;

    public List<string> ListTestResults;

    [TestMethod]
    public void TestMethod1()
    {
        ListTest();
        Assert.IsTrue(ListTestResults.Any());

        YieldTest();
        Assert.IsTrue(YieldTestResults.Any());
    }

    public IEnumerable<string> YieldTest()
    {
        YieldTestResults = new List<string>();
        for (var i = 0; i < 10; i++)
        {
            YieldTestResults.Add(i.ToString(CultureInfo.InvariantCulture));
            yield return i.ToString(CultureInfo.InvariantCulture);
        }
    }

    public IEnumerable<string> ListTest()
    {
        ListTestResults = new List<string>();

        for (var i = 0; i < 10; i++)
        {
            ListTestResults.Add(i.ToString(CultureInfo.InvariantCulture));
        }

        return ListTestResults;
    }
}

故事的寓意:确保如果有一个返回 IEnumerable 的方法,并且您在该方法中使用 yield,那么您有一些东西会迭代结果,或者该方法根本不会执行。

【讨论】:

    【解决方案2】:

    yield return 方法实际上被转换为状态机类,这些状态机类会延迟检索信息 - 仅当您实际请求时。这意味着为了实际提取数据,您必须迭代方法的结果。

    // Gives you an iterator object that hasn't done anything yet
    IEnumerable<MyClass> list = SomeMethod(); 
    
    // Enumerate over the object
    foreach (var item in list ) {
      // Only here will the data be retrieved. 
      // The method will stop on yield return every time the foreach loops.
    }
    

    之所以在第二种情况下运行是因为没有yield块,因此整个方法一次运行。

    在这种特定情况下,您不太可能使用迭代器块而不是常规块,因为您的 GetClassesFromDb() 也不是一个。这意味着它将在第一次运行时同时检索所有数据。当您一次可以访问一个项目时,最好使用迭代器块,因为这样您可以在不再需要它们时停止。

    【讨论】:

      【解决方案3】:

      “yield”版本仅在调用者实际开始枚举返回的集合时“运行”。

      例如,如果您只获得集合:

      var results = SomeObject.SomeMethod (5);
      

      不要对它做任何事情,SomeMethod 将不会执行。

      只有当你开始枚举results集合时,它才会命中。

      foreach (MyClass c in results)
      {
          /* Now it strikes */
      }
      

      【讨论】:

      • 太棒了,正是我需要知道的!谢谢
      猜你喜欢
      • 2017-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-18
      • 2012-03-26
      • 1970-01-01
      • 1970-01-01
      • 2011-01-04
      相关资源
      最近更新 更多