【问题标题】:Manually increment an enumerator inside foreach loop在 foreach 循环中手动增加一个枚举器
【发布时间】:2012-03-04 11:45:16
【问题描述】:

我在 foreach 循环中有一个嵌套的 while 循环,我想在满足某个条件时无限期地推进枚举数。为此,我尝试将枚举器强制转换为 IEnumerator (如果它在 foreach 循环中则必须如此),然后在强制转换的对象上调用 MoveNext() 但它给了我一个错误,说我无法转换它。

无法通过引用转换、装箱转换、拆箱转换、包装转换或空类型转换将类型“System.DateTime”转换为 System.Collections.Generic.IEnumerator。

        foreach (DateTime time in times)
        {
            while (condition)
            {
                // perform action
                // move to next item
                (time as IEnumerator<DateTime>).MoveNext(); // will not let me do this
            }

            // code to execute after while condition is met
         }

在 foreach 循环中手动增加 IEnumerator 的最佳方法是什么?

编辑: 编辑显示在 while 循环之后有代码,一旦满足条件,我想执行,这就是为什么我想在 while 内手动递增然后打破它,而不是 continue 让我回到顶部。如果这不可能,我相信最好的办法是重新设计我的做法。

【问题讨论】:

  • 除了答案之外,您可能还缺少一件事:timeDateTime,它没有实现 IEnumerator
  • 如果foreach循环没有实现IEnumerator接口,它怎么能推进它?
  • 看看你的代码。 foreach (DateTime time in times)times 是被枚举的对象。
  • 枚举器永远不是您的对象之一。否则你不能有多个线程迭代同一个对象

标签: c# loops foreach ienumerable ienumerator


【解决方案1】:

许多其他答案建议使用continue,这很可能会帮助您做您需要做的事情。但是,为了显示手动移动枚举器,首先您必须拥有枚举器,这意味着将循环编写为while

using (var enumerator = times.GetEnumerator())
{
    DateTime time;
    while (enumerator.MoveNext()) 
    {
        time = enumerator.Current;
        // pre-condition code
        while (condition)
        {
            if (enumerator.MoveNext())
            {
                time = enumerator.Current;
                // condition code
            }
            else 
            {
                condition = false;
            }
        }
        // post-condition code
    }
}

来自您的 cmets:

如果没有实现 IEnumerator 接口,foreach 循环如何推进它?

在您的循环中,timeDateTime。它不是需要实现接口或模式才能在循环中工作的对象。 timesDateTime 值的序列,它是必须实现可枚举模式的值。这通常通过实现IEnumerable&lt;T&gt;IEnumerable 接口来实现,它们只需要T GetEnumerator()object GetEnumerator() 方法。这些方法返回一个实现IEnumerator&lt;T&gt;IEnumerator 的对象,它们定义了一个bool MoveNext() 方法和一个Tobject Current 属性。但是time 不能转换为IEnumerator,因为它不是这样的东西,times 序列也不是。

【讨论】:

  • 感谢您的精彩解释!这回答了我关于如何手动增加枚举数的问题,并解释了为什么我的投射不起作用。我假设时间是枚举器,但时间实际上只是保存枚举器 Current 属性值的变量(如果我错了,请纠正我)。这也解释了为什么从未从 IEnumerator 继承的自定义类在 IEnumerable 容器中工作。
  • 注意迭代结束时的非终止while(条件)
  • 在我的一生中,我看不到我的最终代码 sn-p 如何无法完成这项工作。至少你的答案应该警告不要手动组织这样的循环,因为所有的陷阱。面对您回答中的代码,我会假设必须有一个更清晰的解决方案,我会开始分析和重构,直到找到它。
  • @David,我不反对。如果可以选择,我也不会编写此代码!如果有的话,如果我们知道他想做什么,就更容易提出明确的建议。这可能是XY problem 的一个示例,专注于尝试的解决方案而不是实际问题。
【解决方案2】:

您不能从 for 循环内部修改枚举数。语言不允许这样做。您需要使用 continue 语句才能进入循环的下一次迭代。

但是,我不相信您的循环甚至需要继续。继续阅读。

在您的代码上下文中,您需要将 while 转换为 if 以使 continue 引用 foreach 块。

foreach (DateTime time in times)       
{    
     if (condition) 
     {
             // perform action
             continue;
     }
     // code to execute if condition is not met    
}

但是这样写很明显,下面的等效变体仍然更简单

foreach (DateTime time in times)       
{    
     if (condition) 
     {
             // perform action
     }
     else
     {
            // code to execute if condition is not met   
     } 
}

这等效于您的伪代码,因为标记为在满足条件后执行的代码的部分将针对条件为假的每个项目执行。

我对所有这一切的假设是对列表中的每个项目进行条件评估。

【讨论】:

  • 这个sn-p无法完成工作的原因是因为我使用了一个while循环来批处理所有满足条件的项目,然后在条件失败后处理这个批处理。 if 循环每次都会失败,这会产生完全不同的行为,并且它没有回答手动递增枚举数的原始问题。
  • 在 if 中对它们进行批处理,然后在 else 中处理它们。此处代码的行为与您的伪代码相同。您使一个相当简单的循环过于复杂。
  • 我明白你现在的意思了,那行得通。感谢您的意见。
  • 我真的不在乎你接受哪个答案,但我真的希望我能说服你让循环尽可能简单的重要性。感谢您一直坚持我的唠叨!
【解决方案3】:

也许你可以使用continue

【讨论】:

  • 这不是危险。答案不需要以问题的形式表达。自信点!
【解决方案4】:

您将使用 continue 语句: continue;

【讨论】:

    【解决方案5】:

    这只是一个猜测,但听起来您想要做的是获取一个日期时间列表,然后移过所有满足特定条件的日期时间,然后对列表的其余部分执行操作。如果这就是您想要做的,您可能需要 System.Linq 中的 SkipWhile() 之类的东西。例如,以下代码采用一系列日期时间并跳过所有在截止日期之前的日期时间;然后它打印出剩余的日期时间:

    var times = new List<DateTime>()
        {
            DateTime.Now.AddDays(1), DateTime.Now.AddDays(2), DateTime.Now.AddDays(3), DateTime.Now.AddDays(4)
        };
    
    var cutoff = DateTime.Now.AddDays(2);
    
    var timesAfterCutoff = times.SkipWhile(datetime => datetime.CompareTo(cutoff) < 1)
        .Select(datetime => datetime);
    
    foreach (var dateTime in timesAfterCutoff)
    {
        Console.WriteLine(dateTime);
    }
    
    Console.ReadLine();
    

    这就是你想要做的事情吗?

    【讨论】:

    • 好吧,我不想跳过满足条件的项目,我想将它们累积到一个子列表中,稍后再处理。但是,您的 LINQ 参考确实鼓励我再给 LINQ 一个机会(这是我最初使用的),这以更简单的方式解决了我的问题(就像 LINQ 通常那样)。谢谢。
    【解决方案6】:

    我绝对不会容忍我将要提出的建议,但您可以围绕原始 IEnumerable 创建一个包装器,将其转换为返回可用于导航底层枚举器的项目的东西。最终结果可能如下所示。

    public static void Main(string[] args)
    {
      IEnumerable<DateTime> times = GetTimes();
      foreach (var step in times.StepWise())
      {
        while (condition)
        { 
          step.MoveNext();
        }
        Console.WriteLine(step.Current);
      }
    }
    

    然后我们需要创建我们的StepWise扩展方法。

    public static class EnumerableExtension
    {
        public static IEnumerable<Step<T>> StepWise<T>(this IEnumerable<T> instance)
        {
            using (IEnumerator<T> enumerator = instance.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    yield return new Step<T>(enumerator);
                }
            }
        }
    
        public struct Step<T>
        {
            private IEnumerator<T> enumerator;
    
            public Step(IEnumerator<T> enumerator)
            {
                this.enumerator = enumerator;
            }
    
            public bool MoveNext()
            {
                return enumerator.MoveNext();
            }
    
            public T Current
            {
                get { return enumerator.Current; }
            }
    
        }
    }
    

    【讨论】:

      【解决方案7】:

      您可以使用 func 作为您的迭代器,并保留您在该委托中更改的状态,以便在每次迭代时评估。

      public static IEnumerable<T> FunkyIEnumerable<T>(this Func<Tuple<bool, T>> nextOrNot)
          {
              while(true)
              {
                 var result = nextOrNot();
      
                  if(result.Item1)
                      yield return result.Item2;
      
                  else
                      break;
      
              }
      
              yield break;
      
          }
      
          Func<Tuple<bool, int>> nextNumber = () => 
                  Tuple.Create(SomeRemoteService.CanIContinueToSendNumbers(), 1);
      
          foreach(var justGonnaBeOne in nextNumber.FunkyIEnumerable())
                  Console.Writeline(justGonnaBeOne.ToString());
      

      【讨论】:

        【解决方案8】:

        尚未提及的另一种选择是让枚举器返回一个包装器对象,该对象除了被枚举的数据元素外,还允许访问它自己。示例:

        结构 ControllableEnumeratorItem { 私有 ControllableEnumerator 父级; 公共 T 值 {get {return parent.Value;}} public bool MoveNext() {return parent.MoveNext();} 公共 ControllableEnumeratorItem(ControllableEnumerator newParent) {父母=新父母;} }

        希望允许在枚举期间以受控方式修改集合的数据结构也可以使用此方法(例如,通过包含“DeleteCurrentItem”、“AddBeforeCurrentItem”和“AddAfterCurrentItem”方法)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-10-19
          • 2017-09-21
          • 1970-01-01
          • 1970-01-01
          • 2015-12-13
          • 2023-03-31
          • 1970-01-01
          相关资源
          最近更新 更多