Вращать вручную перечислитель внутри цикла foreach - PullRequest
6 голосов
/ 13 февраля 2012

У меня есть вложенный цикл while внутри цикла foreach, где я хотел бы продвигать перечислитель неопределенно, пока выполняется определенное условие. Чтобы сделать это, я пытаюсь привести перечислитель к 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
         }

Каков наилучший способ вручную увеличить IEnumerator внутри цикла foreach?

EDIT: Отредактировано, чтобы показать, что после цикла while есть код, который я хотел бы выполнить, как только будет выполнено условие, поэтому я хотел вручную увеличивать значение в while, а затем прерывать его, а не продолжать, что вернуло бы меня наверх. Если это невозможно, я считаю, что лучше всего изменить дизайн, как я это делаю.

Ответы [ 8 ]

12 голосов
/ 13 февраля 2012

Во многих других ответах рекомендуется использовать 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. Это не тот объект, который должен реализовывать интерфейс или шаблон для работы в цикле. times - это последовательность значений DateTime, которая должна реализовывать перечислимый шаблон. Обычно это достигается путем реализации интерфейсов IEnumerable<T> и IEnumerable, которые просто требуют методов T GetEnumerator() и object GetEnumerator(). Методы возвращают объект, реализующий IEnumerator<T> и IEnumerator, которые определяют метод bool MoveNext() и свойство T или object Current. Но time не может быть приведено к IEnumerator, потому что это не такая вещь, как и последовательность times.

5 голосов
/ 13 февраля 2012

Вы не можете изменять перечислитель внутри цикла for. Язык не позволяет этого. Вам нужно использовать оператор continue для перехода к следующей итерации цикла.

Однако я не уверен, что ваш цикл даже нуждается в продолжении. Читайте дальше.

В контексте вашего кода вам необходимо преобразовать while в if, чтобы продолжить, ссылаясь на блок 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   
     } 
}

Это эквивалентно вашему псевдокоду, потому что часть, помеченная кодом , должна выполняться после выполнения условия выполняется для каждого элемента, для которого условие ложно.

Я предполагаю, что условие оценивается для каждого элемента в списке.

3 голосов
/ 13 февраля 2012

Вы бы использовали оператор continue: continue;

3 голосов
/ 13 февраля 2012

Возможно, вы можете использовать continue?

1 голос
/ 16 февраля 2012

Я определенно не оправдываю то, что собираюсь предложить, но вы можете создать оболочку вокруг оригинального 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; }
        }

    }
}
1 голос
/ 13 февраля 2012

Это всего лишь предположение, но похоже, что вы пытаетесь сделать, взять список дат и пролистать все из них, которые соответствуют определенным критериям, а затем выполнить действие с остальной частью списка.Если это то, что вы пытаетесь сделать, вы, вероятно, хотите что-то вроде SkipWhile () из System.Linq.Например, следующий код берет серию datetime и пропускает все из них, которые находятся перед датой отсечения;затем выводятся оставшиеся даты:

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();

Это то, что вы пытаетесь сделать?

0 голосов
/ 16 февраля 2012

Одна альтернатива, еще не упомянутая, состоит в том, чтобы перечислитель возвращал объект-оболочку, который предоставляет доступ к самому себе в дополнение к перечисляемому элементу данных.Для примера:

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

Этот подход также может использоваться структурами данных, которые хотят, чтобы коллекции могли изменяться контролируемым образом во время перечисления (например, путем включения «DeleteCurrentItem», «AddBeforeCurrentItem» и «AddAfterCurrentItem»методы).

0 голосов
/ 13 февраля 2012

Вы можете использовать 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());
...