手动递增 foreach 循环内的枚举器

本文关键字:枚举 循环 foreach | 更新日期: 2023-09-27 18:24:19

>我在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 内部手动递增然后脱离它而不是继续这会让我回到顶部。如果这是不可能的,我相信最好的办法就是重新设计我的工作方式。

手动递增 foreach 循环内的枚举器

许多其他答案建议使用 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
    }
}

从您的评论中:

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

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

不能从 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   
     } 
}

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

我在所有这些中的假设是,为列表中的每个项目评估条件。

也许你可以使用continue

您可以使用 continue 语句: continue;

这只是一个猜测,但听起来您要做的是获取日期时间列表并跳过所有满足特定条件的时间,然后对列表的其余部分执行操作。如果这就是您要做的,您可能想要来自 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();

这是你想做的那种事情吗?

我绝对不会宽恕我将要提出的建议,但是您可以围绕原始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; }
        }
    }
}

您可以使用 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());

尚未提到的一种替代方法是让枚举器返回一个包装对象,该对象除了允许枚举的数据元素之外访问自身。 对于示例:

struct 可控枚举器项{  私有可控枚举器父级;  公共 T 值 {获取 {返回父级。值;}}  公共布尔 MoveNext(( {返回父级。MoveNext((;}  public ControllableEnumeratorItem(ControllableEnumerator newParent(    {parent = newParent;}}
这种方法

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