Manually increment an enumerator inside foreach lo

2020-04-05 19:39发布

I have a nested while loop inside a foreach loop where I would like to advance the enumerator indefinitately while a certain condition is met. To do this I try casting the enumerator to IEnumerator< T > (which it must be if it is in a foreach loop) then calling MoveNext() on the casted object but it gives me an error saying I cannot convert it.

Cannot convert type 'System.DateTime' to System.Collections.Generic.IEnumerator via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion.

        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
         }

What is the best way to manually increment the IEnumerator inside of the foreach loop?

EDIT: Edited to show there is code after the while loop that I would like executed once the condition is met which is why I wanted to manually increment inside the while then break out of it as opposed to continue which would put me back at the top. If this isn't possible I believe the best thing is to redesign how I am doing it.

8条回答
成全新的幸福
2楼-- · 2020-04-05 19:53

One alternative not yet mentioned is to have an enumerator return a wrapper object which allows access to itself in addition to the data element being enumerated. For sample:

struct ControllableEnumeratorItem<T>
{
  private ControllableEnumerator parent;
  public T Value {get {return parent.Value;}}
  public bool MoveNext() {return parent.MoveNext();}
  public ControllableEnumeratorItem(ControllableEnumerator newParent)
    {parent = newParent;}
}

This approach could also be used by data structures that want to allow collections to be modified in controlled fashion during enumeration (e.g. by including "DeleteCurrentItem", "AddBeforeCurrentItem", and "AddAfterCurrentItem" methods).

查看更多
smile是对你的礼貌
3楼-- · 2020-04-05 19:57

Many of the other answers recommend using continue, which may very well help you do what you need to do. However, in the interests of showing manually moving the enumerator, first you must have the enumerator, and that means writing your loop as a 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
    }
}

From your comments:

How can the foreach loop advance it if it doesn't implement the IEnumerator interface?

In your loop, time is a DateTime. It is not the object that needs to implement an interface or pattern to work in the loop. times is a sequence of DateTime values, it is the one that must implement the enumerable pattern. This is generally fulfilled by implementing the IEnumerable<T> and IEnumerable interfaces, which simply require T GetEnumerator() and object GetEnumerator() methods. The methods return an object implementing IEnumerator<T> and IEnumerator, which define a bool MoveNext() method and a T or object Current property. But time cannot be cast to IEnumerator, because it is no such thing, and neither is the times sequence.

查看更多
成全新的幸福
4楼-- · 2020-04-05 19:59

I definitely do not condone what I am about to suggest, but you can create a wrapper around the original IEnumerable to transform it into something that returns items which can be used to navigate the underlying the enumerator. The end result might look like the following.

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

Then we need to create our StepWise extension method.

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; }
        }

    }
}
查看更多
趁早两清
5楼-- · 2020-04-05 20:03

Perhaps you can use continue?

查看更多
姐就是有狂的资本
6楼-- · 2020-04-05 20:09

You would use the continue statement: continue;

查看更多
够拽才男人
7楼-- · 2020-04-05 20:13

You cannot modify the enumerator from inside the for loop. The language does not permit this. You need to use the continue statement in order to advance to the next iteration of a loop.

However, I'm not convinced that your loop even needs a continue. Read on.

In the context of your code you would need to convert the while to an if in order to make the continue refer to the foreach block.

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

But written like this it is clear that the following equivalent variant is simpler still

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

This is equivalent to your pseudo-code because the part marked code to execute after while condition is met is executed for each item for which condition is false.

My assumption in all of this is that condition is evaluated for each item in the list.

查看更多
登录 后发表回答