【问题标题】:C#: Circular enumeration of IEnumerable<T>C#:IEnumerable<T> 的循环枚举
【发布时间】:2012-05-28 14:45:13
【问题描述】:

我当前的应用程序中有命令层次结构。

public interface ICommand
{
    void Execute();
}

所以,有些命令是有状态的,有些则不是。

我需要以循环方式枚举IEnumerable,以便在命令执行期间执行某些命令。

public class GetNumberCommand : ICommand
{
    public GetNumberCommand()
    {
        List<int> numbers = new List<int>
            {
                1, 2, 3
            };
    }
    
    public void Execute()
    {
        // Circular iteration here.
        // 1 => 2 => 3 => 1 => 2 => 3 => ...
    }

    public void Stop()
    {
        // Log current value. (2 for example)
    }
}

Execute会时不时调用,所以需要存储迭代状态。

如何实现循环枚举?

我找到了两个解决方案:

  1. 使用IEnumerator&lt;T&gt; 接口。 它看起来像:

     if (!_enumerator.MoveNext())
     {
         _enumerator.Reset();
         _enumerator.MoveNext();
     }
    
  2. 使用循环IEnumerable&lt;T&gt;(yield 永远相同的顺序):“Implementing A Circular Iterator” - HonestIllusion.Com。

也许,还有更多方法可以实现。
您会推荐使用什么以及为什么?

【问题讨论】:

  • 为了可重用性,我会实现一个循环迭代器。其实我现在只需要一个,来这里是希望得到一些灵感。但似乎没有一个解决方案似乎有效。好吧,我必须自己实现一个,并将其发布在这里作为答案。希望对其他人有所帮助。

标签: c# ienumerable ienumerator


【解决方案1】:
while (!stop)
{
   foreach (var i in numbers)
   {
     // do something
   }
}

【讨论】:

  • 在我的情况下,Execute会时不时调用,所以需要存储迭代状态。
【解决方案2】:

而不是处理IEnumerator接口,

foreach (var x in GetSomething())
{
     if (someCondition) break;
}



public IEnumerable<int> GetSomething()
{
    List<int> list = new List<int>() { 1, 2, 3 };
    int index=0;

    while (true)
        yield return list[index++ % list.Count];
}

【讨论】:

  • 如果你这样做,我认为你很快就会在索引上溢出
  • 在我的情况下,Execute会时不时调用,所以需要存储迭代状态。
  • 最后看看我的解决方案。您可以将索引存储在集合中并将其传递给迭代器
  • 好的,我明白了。出于好奇,IEnumerable&lt;T&gt;(不是List&lt;T&gt;)怎么样?
  • 我只使用了 List 作为例子,你可以使用任何你想要的 :) 我的例子非常丑陋,不适用于生产代码。它只是用来展示这个想法。
【解决方案3】:

我认为,最舒适的方式是使用自定义枚举器实现自定义集合并在其中封装循环逻辑。

class Collection<T> : IEnumerable<T>
{
  bool circle;

  List<T> collection = new List<T>();

  public IEnumerable<T> IEnumerable<T>.GetEnumerator()
  {
     if(circle) return new CustomEnumerator<T>(this);
     return circle.GetEnumerator();
  }
}

class CustomEnumerator : Enumerator<T> {}

这样的……

【讨论】:

    【解决方案4】:

    你可以编写一个循环枚举而不需要返回。

      public class CircularEnumerable<T> : IEnumerable<T>
      {
        public CircularEnumerable (IEnumerable<T> sequence)
        {
          InfiniteLoop = sequence.Concat (this);
        }
    
        private readonly IEnumerable<T> InfiniteLoop;
    
        public IEnumerator<T> GetEnumerator ()
        {
          return InfiniteLoop.GetEnumerator ();
        }
    
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
        {
          return InfiniteLoop.GetEnumerator ();
        }
      }
    
    public class GetNumberCommand : ICommand
    {
        public GetNumberCommand()
        {
            List<int> numbers = new List<int>
                {
                    1, 2, 3
                };
            infiniteLoopOnNumbers = new CircularEnumerable<int>(numbers).GetEnumerator();
        }
    
        IEnumerator<int> infiniteLoopOnNumbers; 
    
        public void Execute()
        {
            infiniteLoopOnNumbers.MoveNext();
        }
    
        public void Stop()
        {
            int value = infiniteLoopOnNumbers.Current;
        }
    }
    

    【讨论】:

      【解决方案5】:

      这是我刚刚作为扩展实现的一个。

      using System.Collections;
      using System.Collections.Generic;
      using System.Linq;
      
      namespace DroopyExtensions
      {
          public static class CircularEnumaratorExtensionMethod
          {
      
              public static IEnumerator<T> GetCircularEnumerator<T>(this IEnumerable<T> t) 
              {
                  return new CircularEnumarator<T>(t.GetEnumerator());
              }
      
              private class CircularEnumarator<T> : IEnumerator<T>
              {
                  private readonly IEnumerator _wrapedEnumerator;
      
                  public CircularEnumarator(IEnumerator wrapedEnumerator)
                  {
                      this._wrapedEnumerator = wrapedEnumerator;
                  }
      
                  public object Current => _wrapedEnumerator.Current;
      
                  T IEnumerator<T>.Current =>  (T)Current;
      
                  public void Dispose()
                  {
      
                  }
      
                  public bool MoveNext()
                  {
                      if (!_wrapedEnumerator.MoveNext())
                      {
                          _wrapedEnumerator.Reset();
                          return _wrapedEnumerator.MoveNext();
                      }
                      return true;
                  }
      
                  public void Reset()
                  {
                      _wrapedEnumerator.Reset();
                  }
              }
          }   
      } 
      

      要使用它,你所要做的就是

      using DroopyExtensions;
      
      class Program
      {
          static void Main(string[] args)
          {
              var data = new List<string>() {"One", "Two", "Tree"};
              var dataEnumerator = data.GetCircularEnumerator();
              while(dataEnumerator.MoveNext())
              {
                  Console.WriteLine(dataEnumerator.Current);
              }
          }
      }
      

      【讨论】:

      • 我认为你应该交换IEnumerator.Current和CircularEnumarator&lt;T&gt;.Current的实现,因为不需要拆箱。(_wrapedEnumerator应该是IEnumerator&lt;T&gt;而不是IEnumerator)
      【解决方案6】:

      你可以使用这个扩展方法:

      public static IEnumerable<T> Cyclic<T>(this IEnumerable<T> @this)
      {
          while (true)
              foreach (var x in @this)
                  yield return x;
      }
      

      这样:

      public class GetNumberCommand : ICommand
      {
          private readonly IEnumerator<int> _commandState = new[] { 1, 2, 3 }.Cyclic().GetEnumerator();
      
          public void Execute()
          {
              _commandState.MoveNext();
              var state = _commandState.Current;
              
              //
              // Do stuff with state
              //
          }
      
          public void Stop()
          {
              var state = _commandState.Current;                
              // Log state value. (2 for example)
          }
      }
      

      【讨论】:

      • 看来您的解决方案是最好的(简单明了)。它与使用IEnumerator&lt;T&gt; 接口结合迭代的解决方案#2(见问题)相同。
      • 只是给读者的 TODO 笔记。必须实施对IEnumerator&lt;T&gt; 实例的适当处置。
      猜你喜欢
      • 1970-01-01
      • 2017-01-09
      • 2020-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-30
      相关资源
      最近更新 更多