Шаблон для использования IEnumerator <T>в интерфейсах - PullRequest
3 голосов
/ 06 июля 2010

У меня есть класс C #, который должен обрабатывать последовательность элементов (IEnumerable<T>) в нескольких методах, поэтому я не могу просто foreach внутри метода.Я звоню .GetEnumerator() и передаю это IEnumerator<T>, и это прекрасно работает, давая мне гибкость, необходимую мне при циклическом просмотре одной последовательности.

Теперь я хочу позволить другим добавить логику в этот процесс.Самый естественный способ сделать это - дать им интерфейс с методом, который принимает IEnumerator<T>.Легко, сделано, и это работает.

Но я обеспокоен тем, что это анти-паттерн.Они должны знать, что IEnumerator<T> уже вызвал .MoveNext(), поэтому они могут просто получить доступ к .Current.Кроме того, я не вижу прецедента для использования IEnumerator<T> в интерфейсах, которые будут реализованы.

  1. Какие подводные камни я не рассматриваю?
  2. Есть ли другой шаблон, который позволит мне этотот же самый эффективный механизм (т.е. я не хочу, чтобы несколько копий создавались / уничтожались) без показа самого IEnumerator<T>?

Обновление: Как яупомянуто в комментарии ниже: я хочу что-то общее Stream<T>.Мне нужно иметь возможность эффективно видеть следующий элемент (IEnumerator.Current -> .Peek()) и потреблять его (IEnumerator<T>.MoveNext() -> .Pop()).

Я использовал IEnumerator<T>, потому что он соответствуетинтерфейс счета мудрый.Я предпочитаю использовать общие типы BCL, когда они подходят, но мне показалось, что я злоупотреблял этим.

Итак, вопрос 3) Есть ли класс, который соответствует этой потребности?Или я должен просто создать свой собственный поток, который лениво выполняет IEnumerator<T> внутри?Тогда это было бы полностью заключено в капсулу.Я бы не хотел использовать многие из существующих коллекций, поскольку они имеют внутреннее хранилище, тогда как я хотел бы, чтобы хранилище было IEnumerable<T> iteslf.


ОК, похоже, что консенсус заключается в том, чтосделайте, чтобы IEnumerator<T> часто был ValueType, а также не зная априори о состоянии IEnumerator<T>, что, как правило, плохая идея обойти его.

Лучшее предложение, которое у меня естьСлышал, чтобы создать свой собственный класс, который проходит вокруг.Любые другие предложения?

Ответы [ 4 ]

6 голосов
/ 06 июля 2010

Вы должны определенно не передавать IEnumerator<T>. Помимо всего прочего, он может иметь некоторые очень странные эффекты в некоторых случаях. Что бы вы ожидали здесь, например?

using System;
using System.Collections.Generic;

class Test
{
    static void ShowCurrentAndNext(IEnumerator<int> iterator)        
    {
        Console.WriteLine("ShowCurrentAndNext");
        Console.WriteLine(iterator.Current);
        iterator.MoveNext(); // Let's assume it returns true
        Console.WriteLine(iterator.Current);
    }

    static void Main()
    {
        List<int> list = new List<int> { 1, 2, 3, 4, 5 };
        using (var iterator = list.GetEnumerator())
        {
            iterator.MoveNext(); // Get things going
            ShowCurrentAndNext(iterator);
            ShowCurrentAndNext(iterator);
            ShowCurrentAndNext(iterator);
        }
    }
}

Несколько изменений, чтобы попробовать:

using (List<int>.Enumerator iterator = list.GetEnumerator())

и

using (IEnumerator<int> iterator = list.GetEnumerator())

Попробуйте предсказать результаты в каждом случае:)

По общему признанию, это особенно злой пример, но он демонстрирует некоторые угловые случаи, связанные с обходом изменяемого состояния. Я настоятельно рекомендую вам выполнить все итерации в «центральном» методе, который вызывает соответствующие другие методы только с текущим значением.

2 голосов
/ 06 июля 2010

Мне кажется, что вы могли бы извлечь выгоду из использования события, чтобы вы могли отправлять уведомления об элементах, подлежащих обработке, слушателям.Регулярные события .NET обрабатываются в том порядке, в котором они подписаны, поэтому вы можете прибегнуть к более явному подходу, если требуется упорядочение.

Вы также можете посмотреть на Reactive Framework.

2 голосов
/ 06 июля 2010

Я бы настоятельно рекомендовал не передавать сам счетчик;какая у вас причина для этого, кроме необходимости в текущем значении?

Если я не упускаю что-то очевидное, я бы порекомендовал, чтобы ваши служебные функции просто принимали тип, который вы перечисляете, в качестве параметра, затемиметь один внешний foreach цикл, который обрабатывает фактическое перечисление.

Возможно, вы сможете предоставить некоторую дополнительную информацию о том, почему вы до сих пор принимали это проектное решение.

1 голос
/ 06 июля 2010

Если я правильно понимаю, у вас есть несколько методов, которые могут вызывать MoveNext для последовательности, и вы хотите, чтобы эти методы взаимодействовали друг с другом, поэтому вы передаете IEnumerator<T>. Как вы уже упоминали, здесь определенно существует тесная связь, поскольку вы ожидаете, что перечислитель будет находиться в определенном состоянии при входе в каждый метод. Похоже, что то, что вам действительно нужно, это что-то вроде класса Stream, который является как коллекцией (своего рода), так и итератором (имеет представление о текущем местоположении). Я бы обернул вашу итерацию и любое другое нужное вам состояние в вашем собственном классе и использовал бы различные методы в качестве членов этого класса

...