2010-11-15 2 views
15

Я создал пользовательскую общую очередь, которая реализует общий интерфейс IQueue, который использует общую очередь из пространства имен System.Collections.Generic как частную внутреннюю очередь. Пример был очищен от нерелевантного кода.Если IEnumerable итератор в очереди деактивирует элемент

public interface IQueue<TQueueItem> 
{ 
    void Enqueue(TQueueItem queueItem); 
    TQueueItem Dequeue(); 
} 

public class CustomQueue<TQueueItem> : IQueue<TQueueItem> 
{ 
    private readonly Queue<TQueueItem> queue = new Queue<TQueueItem>(); 
    ... 
    public void Enqueue(TQueueItem queueItem) 
    { 
     ... 
     queue.Enqueue(queueItem); 
     ... 
    } 

    public TQueueItem Dequeue() 
    { 
     ... 
     return queue.Dequeue(); 
     ... 
    } 
} 

Я хочу, чтобы держать вещи в соответствии с основными реализациями и заметили, что очереди ядро ​​реализует IEnumerable, так что я буду делать то же самое либо явно реализует IEnumerable в классе или наследовать его с IQueue интерфейсом.

Что я хочу знать, когда перечисление по очереди должно каждое перемещение следующего деактивировать следующий элемент? Я использовал рефлектор, чтобы увидеть, как Microsoft это сделала, и все, что они делают, - это пройти через частный массив очередей, но Microsoft далека от непогрешимости, поэтому я хотел получить общее мнение.

public class CustomQueue<TQueueItem> : IQueue<TQueueItem>, IEnumerable<TQueueItem> 
{ 
    ... 

    public IEnumerator<TQueueItem> GetEnumerator() 
    { 
     while (queue.Count > 0) 
     { 
      yield return Dequeue(); 
     } 
    } 

    //Or 

    public IEnumerator<TQueueItem> GetEnumerator() 
    { 
     return queue.GetEnumerator(); 
    } 

    ... 
} 

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

EDIT

Чтобы поместить вещи в контекст. Класс, который я реализую, выполняет Monitor.Wait, когда Dequeuing и нет элементов в очереди. Когда элемент помещается в очередь, появляется Monitor.Pulse. Это позволяет одному потоку подталкивать материал в очередь, а другой - «смотреть» очередь.

С точки зрения кодирования я пытаюсь решить, какой она выглядит чище:

foreach(QueueItem item in queue) 
{ 
    DoSomethingWithThe(item); 
} 

//Or 

while(systemIsRunning) 
{ 
    DoSomethingWithThe(queue.Dequeue()); 
} 

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

EDIT

Интересно достаточно я нашел в блоге, где кто-то сделал именно это.

http://blogs.msdn.com/b/toub/archive/2006/04/12/blocking-queues.aspx

EDIT

Один последний удар в этом, прежде чем я закрыть это прочь. Как люди чувствуют, что класс не реализует IEnumerable, но имеет метод IEnumerator GetEnumerator(), который отменяет элементы? Язык .net поддерживает утиную печать, foreach является одним из видов использования. Возможно, это заслуживает собственного вопроса?

EDIT

поднял вопрос о реализации методы GetEnumerator без реализации IEnumerable в другом question.

+0

Я согласен с тем, что я рассматривал, делая злоупотребления, поэтому причина для публикации, мне просто нужно было сказать 10 или 11 раз. Я думаю, что реальный вопрос, который я должен задать себе, - это почему мне нужно реализовать IEnumerable? Я делаю это, потому что основная реализация выполняется, поскольку у меня нет реальных требований для повторной итерации моей очереди. – Bronumski

+0

К сожалению, я не могу отметить всех вас как правильно, поэтому я дам вам весь день, чтобы очистить ваши ответы и отметить наивысшее проголосовавшее как правильное. – Bronumski

ответ

18

Итераторы всегда должны быть идемпотентными, то есть не изменять очередь при повторении по ней.

Нет гарантий, что не будет двух одновременных итераций ...


Edit для решения новых комментариев:

Когда другой программист (например, ваше будущее я;)) приходит, чтобы добавить функции к коду, они не могут считать, что итераторы Одноразовый , Они могут добавить оператор журнала, который перечисляет то, что находится в очереди, перед его использованием (oops).

Еще одна вещь, о которой я только подумал, заключается в том, что отладчик визуальной студии часто перечисляет ваши классы для отображения. Это вызовет некоторые чрезвычайно запутывающие ошибки :)

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

+0

Я согласен, что Итераторы должны быть идемпотентными, мне просто интересно, есть ли очередь, где вы можете разбить форму. В моем случае (см. Править) было бы неважно, было ли несколько повторений процессов в очереди, и на самом деле это, вероятно, будет. – Bronumski

+1

Это еще проще, чем проблема параллелизма. IEnumerables доступны только для чтения, только для чтения. Изменение базовой бетонной коллекции может привести к тому, что реализации IEnumerable будут «потеряны», и поэтому большинство из них не позволяют этого. Возьмите список, например. Подключите его к foreach, который удаляет каждый элемент. В первом обращении индекс 0 исчезает, а индекс 1 становится индексом 0. Когда перечислитель вызывает MoveNext, он попытается перейти от индекса 0 к индексу 1, который теперь фактически является индексом 2. Это приведет к нежелательному поведению. – KeithS

+3

@ В целом это отличный момент, но в коде OP, реализация перечислителя не проявляет этой конкретной проблемы. –

0

Итерация через очередь должна NOT dequeue.

Это предназначено для просмотра содержимого очереди, чтобы не деактивировать. Так же работает MSMQ или MQ Series.

0

Я не думаю, что он должен. Это было бы очень неявным, и оно не сообщает об этом намерении любому, кто используется для использования .net framework.

0

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

2

Я бы сказал no, сделайте это второй способ.

Это не только совместимо со встроенным классом Queue, но также более согласуется с тем, что интерфейс IEnumerable<T> предназначен только для чтения.

Кроме того, делает это на самом деле, кажется интуитивным вам ?:

//queue is full 
foreach(var item in queue) 
    Console.WriteLine(item.ToString()); 
//queue is empty!? 
+0

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

+0

@Bronumski: Поскольку это вопрос только одной строки кода, я думаю, что реальный вопрос: почему бы и нет? Я мог бы, конечно, представить себе возможность перечислять (или особенно использовать Linq on) элементы очереди, чтобы быть полезными, поэтому я действительно не понимаю, почему вы этого не сделали. –

+0

Конечно, для базового API я мог видеть, что это полезно, но, развиваясь из подхода Agile и принимая, я не собираюсь злоупотреблять языком, который мне придется называть YAGNI. – Bronumski

0

У меня был код, где propperty прибудет не идемпотентен ... Это была такая боль, чтобы расшифровать. Пожалуйста, придерживайтесь руководства Dequeue.

Также вы можете быть не единственным, кто работает в очереди, чтобы он мог запутаться с более чем одним потребителем.

12

Абсолютно положительно, вы не должны мутировать коллекцию, когда вы ее итерации. Целый пункт итераторов состоит в том, что они обеспечивают неразрушающий просмотр только для чтения коллекции. Было бы очень удивительно, если бы кто-нибудь использовал ваш код, который смотрит на него, он меняет его.

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

+2

Справедливая точка, которую я не рассматривал. – Bronumski

+0

Возможно, вам будет интересно увидеть некоторые ответы на мой другой вопрос http://stackoverflow.com/questions/4194900/should-a-class-that-has-a-getenumerator-method-but-does-not-implement -inumerable – Bronumski

+0

-1 Почему тогда [BlockingCollection .GetConsumingEnumerable Method] (http://msdn.microsoft.com/en-us/library/vstudio/dd287186 (v = vs.100) .aspx) удаляет и вернуть предметы из коллекции? Это абсолютно позитивно мутирует коллекцию, когда вы ее повторяете. –

5

Я бы предположил, что у вас может быть метод, называемый DequeueAll, который возвращает элемент класса с функцией GetEnumerator, который представляет все в очереди, и очищает очередь (если элемент очереди добавляется вокруг при создании iEnumerable новый элемент должен либо отображаться в настоящем все в AllItemsDequued, а не в очереди или в очереди, но не в текущем вызове).Если этот класс реализует iEnumerable, он должен быть сконструирован таким образом, чтобы возвращаемый объект оставался в силе даже после того, как был создан и удален нумератор (позволяя его перечислять несколько раз). Если это было бы непрактично, может быть полезно дать классу имя, которое предполагает, что объекты класса не должны сохраняться. Можно все же сделать

foreach(QueueItem theItem in theQueue.DequeueAll()) {}
, но с меньшей вероятностью (неправомерно) сохранить результат Queue.DequeueAll в iEnumerable. Если вам нужна максимальная производительность, позволяя результат QQueue.DequeueAll использоваться как iEnumerable, можно было бы определить расширенный отрисовку, которая сделала бы снимок результата DequeueAll (таким образом позволяя отбрасывать старые элементы).

+0

Мне это нравится, думая нестандартно. – Bronumski

1

Строго говоря, очередь обеспечивает только push-back, pop-front, is-empty и, может быть, get-size операции. Все, что вы добавляете рядом с этим, не является частью очереди, поэтому, если вы решите предоставить дополнительные операции, вам не нужно придерживаться семантики очереди.

В частности, итераторы не являются частью стандартного интерфейса очереди, поэтому вам не нужно, чтобы они удаляли текущий текущий элемент. (Как указывали другие, это будет противоречить ожиданиям итераторов.)

+0

Я думаю, что это было частью моей проблемы, я смотрел на нее с чистой точки зрения, где очередь должна только толкать и поп. Хорошее объяснение. – Bronumski

3

Я собираюсь оставить победителя позади и сказать да. Это кажется разумным подходом. Хотя я должен сделать одно предложение. То есть не реализуйте этот потребляющий итератор в методе GetEnumerator. Вместо этого назовите его GetConsumingEnumerator или что-то подобное. Таким образом, очевидно, что произойдет, и механизм foreach не будет использовать его по умолчанию. Вы не будете первым, кто это сделает. Фактически, Microsoft уже делает это в BCL через класс BlockingCollection, и они даже использовали GetConsumingEnumerator как метод . Я думаю, вы знаете, что я собираюсь предложить в следующий раз?

Как вы думаете, что я придумал название внушения? Почему бы не просто использовать BlockingCollection? Он делает все, о чем вы просили, и многое другое.

+0

Я заметил интерфейс IProducerConsumerCollection , но я никогда не замечал BlockingCollection , спасибо за головы. – Bronumski

 Смежные вопросы

  • Нет связанных вопросов^_^