Вопрос

У меня есть класс 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>, что, как правило, передавать это по кругу - плохая идея.

Лучшее предложение, которое я слышал, - это создать свой собственный класс, который передается по кругу.Есть еще какие-нибудь предложения?

Это было полезно?

Решение

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

Другие советы

Ты должен определенно не пройти мимо 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())

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

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

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

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

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

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

Возможно, вам также захочется взглянуть на Реактивный фреймворк.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top