【问题标题】:Yield in C#在 C# 中生成
【发布时间】:2009-06-10 21:42:58
【问题描述】:

这在 c 中有什么等价物吗?

【问题讨论】:

  • 为什么需要yield?如果你有一个数组,你可以遍历它。 Yield 与 IEnumerables 相关,您通常会在“foreach”情况下使用它。既然你不能'foreach',为什么你需要创建一个'yield'?

标签: c# .net c++ c


【解决方案1】:

Yield 由编译器实现为实现状态机的自定义类。虽然您无法轻松获得语法(除非您使用之前指定的 Fiber 方法),但您可以非常简单地自己复制结果,尽管这很乏味。下面是方法(我将在 C# 中展示,您必须根据您使用的类型在 C++ 中做适当的事情):

假设如下代码:

public IEnumerable<T> GetOddStrings(
  IEnumerable<IEnumerable<string>> stringCollections)
{
    foreach(var stringCollection in stringCollections)
      foreach(var str in stringCollection)
      {
        if(str.Length %2 != 0) yield return str;
        if(str.Length == 42) yield break;  // 42 is BAD! Stop immediately
      }
}

1) 将所有 foreach 方法展开为显式枚举器调用:

public IEnumerable<T> GetOddStrings(
  IEnumerable<IEnumerable<string>> stringCollections)
{
  var firstEnumerator = stringCollection.GetEnumerator();
  while(firstEnumerator.MoveNext())
  {
    var secondEnumerator = firstEnumerator.Current.GetEnumerator();
    while(secondEnumerator.MoveNext())
    { 
      var str= secondEnumerator.Current;
      if(str.Length %2 != 0) yield return str;
      if(str.Length == 42) yield break;
    }
  }
}

2) 将所有局部变量移动到方法的顶部:

public IEnumerable<T> GetOddStrings(
  IEnumerable<IEnumerable<string>> stringCollections)
{
  IEnumerator<IEnumerable<string>> firstEnumerator;
  IEnumerator<string> secondEnumerator;
  string str;

  firstEnumerator = stringCollections.GetEnumerator();
  while(firstEnumerator.MoveNext())
  {
    secondEnumerator = firstEnumerator.Current.GetEnumerator();
    while(secondEnumerator.MoveNext())
    { 
      str= secondEnumerator.Current;
      if(str.Length %2 != 0) yield return str;
      if(str.Length == 42) yield break;
    }
  }
}

3) 移动到带有嵌套 switch 语句的循环结构。
a) 更改状态并为每个收益返回继续循环。 b) 反转 if 条件 c) 每个退出条件的屈服中断(下面我们反转 if)。

public IEnumerable<T> GetOddStrings(
  IEnumerable<IEnumerable<string>> stringCollections)
{
  IEnumerator<IEnumerable<string>> firstEnumerator;
  IEnumerator<string> secondEnumerator;
  string str;
  int state = 0;

  while(true)
  {
    switch(state)
    {
      case 0:
        firstEnumerator = stringCollections.GetEnumerator();
        // This could be "yield break" but I want to show how you
        // could split ifs with significant code in the else
        if(!firstEnumerator.MoveNext()) 
        { 
          state = 1; 
          continue; 
        }

        secondEnumerator = firstEnumerator.Current;
        if(!secondEnumerator.MoveNext) continue;
        state = 2;
        if(str.Length %2 != 0) yield return str;
        continue;

      case 1:
        yield break;

      case 2:
        if(str.Length == 42) yield break;
        state = 0;
        continue;
    }
  }
}

4) 进入一个类并从您的方法中返回该类: a)yield break 变成“return false”; b) 收益回报变成“this.Current = ??; return true;”

public IEnumerable<T> GetOddStrings(
  IEnumerable<IEnumerable<string>> stringCollections)
{
  return new OddStringEnumerable(stringCollections);
}

private class OddStringEnumerable : IEnumerable<string>
{
  IEnumerable<IEnumerable<string>> stringCollections;
  IEnumerator<IEnumerable<string>> firstEnumerator;
  IEnumerator<string> secondEnumerator;
  string str;
  int state;

  public OddStringEnumerable(IEnumerable<IEnumerable<string>> stringCollections)
  {
    this.stringCollections = stringCollections;
  }

  public string Current { get; private set; }

  public bool MoveNext()
  {
    while(true)
    {
      switch(state)
      {
        case 0:
          firstEnumerator = this.stringCollections.GetEnumerator();
          if(!this.firstEnumerator.MoveNext()) 
          { 
            this.state = 1; 
            continue; 
          }

          this.secondEnumerator = this.firstEnumerator.Current;
          if(!secondEnumerator.MoveNext) continue;

          this.state = 2;
          if(str.Length %2 != 0) 
          {
            this.Current = str;
            return true;
          }
          continue;

        case 1:
          return false;

        case 2:
          if(str.Length == 42) return false;
          this.state = 0;
          continue;
      }
    }
  }
}

5) 酌情优化。

【讨论】:

    【解决方案2】:

    纤维?哦,这个:

    使用 Fibers 的原生 C++ 的收益返回迭代器
    http://www.codeproject.com/KB/library/fiber-based_iterator.aspx

    【讨论】:

      【解决方案3】:

      虽然 C 在枚举集合方面没有与 yield 相同的概念,但它确实具有创建协程和纤维的能力。

      这里有一些可能感兴趣的维基百科文章:

      http://en.wikipedia.org/wiki/Setcontext http://en.wikipedia.org/wiki/Setjmp/longjmp

      【讨论】:

      • 我认为这太复杂了。最好的方法是只创建一个包含 yieldindex 变量和数组的结构,然后每次“yield”时增加索引。这样你的'yield'只是从yieldindex点中提取数组值。
      • 我并不是说它不复杂,只是那是 C 的等价物。有很多方法可以解决这个问题...这就是我们编写软件的原因 =)
      【解决方案4】:

      Coroutines in C 使用了一些预处理程序,但实现了一个相当自然的(相对于 C 中的任何其他内容)yield

      而你会用 Python 写这个:

      """This is actually a built-in function.
      def range(start, stop, step):
          i = start
          while i < stop:
              yield i
              i = i + step
      """
      
      if __name__ == '__main__':
          import sys
          start = int(sys.argv[1]) if len(sys.argv) > 2 else 0
          stop = int(sys.argv[2]) if len(sys.argv) > 2 else int(sys.argv[1])
          step = int(sys.argv[3]) if len(sys.argv) > 3 else 1
          for i in range(start, stop, step):
              print i,
          print
      

      coroutine.h 让您可以用 C 语言编写:

      #include <coroutine.h>
      #include <stdio.h>
      
      int range(int start, int stop, int step) {
          static int i;
      
          scrBegin;
          for (i = start; i < stop; i += step)
              scrReturn(i);
          scrFinish(start - 1);
      }
      
      int main(int argc, char **argv) {
          int start, stop, step, i;
      
          start = argc > 2 ? atoi(argv[1]) : 0;
          stop = argc > 2 ? atoi(argv[2]) : atoi(argv[1]);
          step = argc > 3 ? atoi(argv[3]) : 1;
      
          while ((i = range(start, stop, step)) >= start)
              printf("%d ", i);
          printf("\n");
      }
      
      $ cc 范围.c $ ./a.out 10 0 1 2 3 4 5 6 7 8 9

      对于更复杂且需要重入的东西,Python 中的Hamming numbers

      def hamming():
          yield 1
      
          i2 = (2*x for x in hamming())
          i3 = (3*x for x in hamming())
          i5 = (5*x for x in hamming())
      
          m2, m3, m5 = i2.next(), i3.next(), i5.next()
      
          while True:
              if m2 < m3:
                  if m2 < m5:
                      yield m2
                      m2 = i2.next()
                  else:
                      if m2 > m5: yield m5
                      m5 = i5.next()
              elif m2 == m3: m3 = i3.next()
              elif m3 < m5:
                  yield m3
                  m3 = i3.next()
              else:
                  if m3 > m5: yield m5
                  m5 = i5.next()
      
      if __name__ == '__main__':
          import sys
          it = hamming()
          for i in range(str(sys.argv[1]) if len(sys.argv) > 1 else 25):
              print it.next(),
          print
      

      和C:

      #include <coroutine.h>
      #include <stdio.h>
      
      int hamming(ccrContParam) {
          ccrBeginContext;
          ccrContext z[3];
          int m2, m3, m5;
          ccrEndContext(state);
      
          ccrBegin(state);
          state->z[0] = state->z[1] = state->z[2] = 0;
          ccrReturn(1);
      
      #define m2_next (2*hamming(&state->z[0]))
      #define m3_next (3*hamming(&state->z[1]))
      #define m5_next (5*hamming(&state->z[2]))
      
          state->m2 = m2_next, state->m3 = m3_next, state->m5 = m5_next;
      
          while (1) {
              if (state->m2 < state->m3) {
                  if (state->m2 < state->m5) {
                      ccrReturn(state->m2);
                      state->m2 = m2_next;
                  } else {
                      if (state->m2 > state->m5) ccrReturn(state->m5);
                      state->m5 = m5_next;
                  }
              } else if (state->m2 == state->m3) state->m3 = m3_next;
              else if (state->m3 < state->m5) {
                  ccrReturn(state->m3);
                  state->m3 = m3_next;
              } else {
                  if (state->m3 > state->m5) ccrReturn(state->m5);
                  state->m5 = m5_next;
              }
          }
          ccrFinish(-1);
      }
      
      int main(int argc, char **argv) {
          int count = argc > 1 ? atoi(argv[1]) : 25, i;
          ccrContext z = 0;
      
          for (i = 0; i < count; i++)
              printf("%d ", hamming(&z));
          printf("\n");
      }
      
      $ cc hamming.c $ ./a.out 1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36 40 45 48 50 54

      【讨论】:

        【解决方案5】:

        没有。不过,在 Windows 上,您可以使用纤维来实现类似的效果。

        【讨论】:

          【解决方案6】:

          没有。但是,您可以使用setjmp, longjmp 在 C 中实现类似的效果,但这非常很棘手。

          【讨论】:

            【解决方案7】:

            在 C# 中,yield 简化了为集合创建 IEnumberables。

            在 C++ 中,您必须使用 STL 迭代器。

            【讨论】:

              猜你喜欢
              • 2015-04-16
              • 2010-09-26
              • 1970-01-01
              • 2018-11-10
              • 2012-03-14
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多