【问题标题】:Why can't "return" and "yield return" be used in the same method?为什么“return”和“yield return”不能在同一个方法中使用?
【发布时间】:2012-03-26 17:39:34
【问题描述】:

为什么我们不能在同一个方法中同时使用 return 和 yield return?

例如,我们可以在下面有 GetIntegers1 和 GetIntegers2,但不能有 GetIntegers3。

public IEnumerable<int> GetIntegers1()
{
  return new[] { 4, 5, 6 };
}

public IEnumerable<int> GetIntegers2()
{
  yield return 1;
  yield return 2;
  yield return 3;
}

public IEnumerable<int> GetIntegers3()
{
  if ( someCondition )
  {
    return new[] {4, 5, 6}; // compiler error
  }
  else
  {
    yield return 1;
    yield return 2;
    yield return 3;
  }
}

【问题讨论】:

  • 等一下,乔恩·斯基特马上就来。
  • 我会补充一点,如果您真的需要它,您可以创建一个 GetIngegers4,根据条件调用 GetIntegers1 或 GetIntegers2。
  • 这可能很明显,但在这种情况下,您总是可以展开您的集合并返回项目:foreach(var item in new[]{4,5,6}) yield return item;

标签: c# return ienumerable yield-return


【解决方案1】:

return 很渴望。它一次返回整个结果集。 yield return 构建一个枚举器。当您使用yield return 时,C# 编译器会在幕后为枚举器发出必要的类。编译器在确定是否应该发出可枚举代码或具有返回简单数组的方法时,不会查找诸如if ( someCondition ) 之类的运行时条件。它检测到在你的方法中你同时使用了这两种方法,这是不可能的,因为他不能为枚举器发出代码,同时让方法返回一个普通数组,所有这些都用于同一个方法。

【讨论】:

  • 当我尝试调试代码时,我可以看到它遍历了迭代中的所有代码行。那怎么能说它在内部构建枚举器并且只返回一次......
【解决方案2】:

不,你不能这样做 - 迭代器块(带有 yield 的东西)不能使用常规(非屈服)return。相反,您需要使用 2 种方法:

public IEnumerable<int> GetIntegers3()
{
  if ( someCondition )
  {
    return new[] {4, 5, 6}; // compiler error
  }
  else
  {
    return GetIntegers3Deferred();
  }
}
private IEnumerable<int> GetIntegers3Deferred()
{
    yield return 1;
    yield return 2;
    yield return 3;
}

或者由于在这种特定情况下,两者的代码已经存在于其他两种方法中:

public IEnumerable<int> GetIntegers3()
{
  return ( someCondition ) ? GetIntegers1() : GetIntegers2();
}

【讨论】:

    【解决方案3】:

    编译器使用yield 语句(返回或中断)重写任何方法。它目前无法处理可能会或可能不会yield 的方法。

    我建议阅读 Jon Skeet 的 C# in Depth 的第 6 章,其中第 6 章是免费提供的 - 它很好地涵盖了迭代器块。

    我看不出为什么这在未来版本的 c# 编译器中是不可能的。其他 .Net 语言确实以“yield from”运算符 (See F# yield!) 的形式支持类似的东西。如果 c# 中存在这样的运算符,它将允许您以以下形式编写代码:

    public IEnumerable<int> GetIntegers()
    {
      if ( someCondition )
      {
        yield! return new[] {4, 5, 6};
      }
      else
      {
        yield return 1;
        yield return 2;
        yield return 3;
      }
    }
    

    【讨论】:

      【解决方案4】:

      理论上我认为没有理由不能将return和yield return混在一起:编译器首先将任何return (blabla());语句从语法上转换为:

      var myEnumerable = blabla();
      foreach (var m in myEnumerable) 
          yield return m; 
      yield break; 
      

      然后继续(将整个方法转换为......他们现在对其进行的任何转换;一个内部匿名 IEnumerator 类?!)

      那他们为什么不选择实现呢,这里有两个猜测:

      • 他们可能已经决定同时获得回报和收益回报会让用户感到困惑,

      • 返回整个 enumerable 更快,更便宜,但也急切;通过 yield return 构建会稍微贵一些(特别是如果递归调用,请参阅 Eric Lippert 的警告,在此处使用 yield return 语句在二叉树中进行遍历:https://stackoverflow.com/a/3970171/671084 例如)但懒惰。所以用户通常不想混合这些:如果你不需要懒惰(即你知道整个序列)不要遭受效率损失,只需使用普通方法。他们可能想强迫用户按照这些思路思考。

      另一方面,在某些情况下,用户似乎可以从某些句法扩展中受益;您可能想以这个问题和答案为例(不是同一个问题,但可能有类似的动机):Yield Return Many?

      【讨论】:

      • 这实际上是对“微软决定不支持的原因是什么......”的回答,这本来是一个更好的问题。
      【解决方案5】:

      我认为它不起作用的主要原因是因为以一种不会过于复杂但同时具有高性能的方式设计它会很困难,而且收益相对较小。

      您的代码究竟会做什么?它会直接返回数组,还是会遍历它?

      如果它直接返回数组,那么你必须想出复杂的规则,在什么条件下return是允许的,因为yield return之后的return没有意义。而且您可能需要生成复杂的代码来决定该方法是返回自定义迭代器还是返回数组。

      如果您想迭代集合,您可能需要一些更好的关键字。 Something like yield foreach。这实际上是考虑过的,但最终没有实施。我想我记得阅读的主要原因是,如果您有多个嵌套迭代器,实际上很难让它表现良好。

      【讨论】:

      • 很难用嵌套迭代器让它表现良好,但这个问题可以通过一些巧妙的方式解决;他们在 C-Omega 中这样做了。但是,如果您这样做,那么当这些嵌套迭代器包含异常处理时,您就会开始遇到正确性问题。这是一个可爱的功能创意,我希望我们能做到,但付出与收获的比率太高了。
      猜你喜欢
      • 2020-05-30
      • 1970-01-01
      • 1970-01-01
      • 2017-11-26
      • 2017-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多