2010-11-15 17 views
15

he creado una cola genérica personalizada que implementa una interfaz genérica de IQueue, que utiliza la cola genérica del espacio de nombres System.Collections.Generic como cola interna privada. El ejemplo se ha eliminado del código irrelevante.En caso de que un iterador de IEnumerable en una cola dequeue un elemento

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

que quieren mantener las cosas consistentes con las implementaciones básicas y se han dado cuenta de que la cola del núcleo implementa IEnumerable por lo que hará lo mismo, ya sea de forma explícita mediante la implementación de IEnumerable en la clase o heredarlo con la interfaz iQueue.

Lo que quiero saber es cuándo enumerar en la cola cada movimiento siguiente dequeue el siguiente elemento? He usado reflector para ver cómo lo ha hecho Microsoft y todo lo que hacen es pasar por la matriz privada de colas, pero Microsoft está lejos de ser infalible, así que quería obtener una opinión general.

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

    ... 
} 

Estoy en dos mentes, por un lado, siento que iteración a través de una colección no debe cambiar el estado de las colecciones, pero por otro lado, y sobre todo con mi aplicación particular, esto haría que el uso de un aspecto limpio.

EDIT

Para poner las cosas en contexto. La clase que estoy implementando hace un Monitor. Espere al Dequeer y no haya elementos en la cola. Cuando un artículo se coloca en la cola, hay un Monitor.Pulse. Esto permite que un hilo empuje cosas en la cola y el otro esencialmente "mirar" la cola.

Desde el punto de vista de codificación que estoy tratando de decidir qué se ve más limpia:

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

//Or 

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

Por mi aplicación particular, no importaría si hubiera varios elementos desencola proceso. Debido a que es una cola, ambos pueden elegir un elemento, ya que ningún elemento debe procesarse más de una vez, de ahí el uso de una cola.

EDITAR

Curiosamente he encontrado una entrada de blog en la que alguien ha hecho exactamente esto.

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

EDITAR

Una última puñalada en esto antes de cerrar esto. ¿Cómo se sienten las personas acerca de la clase que no implementan IEnumerable sino que tienen un método IEnumerator GetEnumerator() que dequeues items? El lenguaje .net admite el tipado de pato, ya que uno de los usos es foreach. Tal vez esto merece su propia pregunta?

EDITAR

han planteado la cuestión de la aplicación de un método GetEnumerator sin implementar IEnumerable en otro question.

+0

Acepto lo que estaba considerando hacer era un abuso, de ahí el motivo de la publicación, solo necesitaba que me lo dijeran 10 u 11 veces. Creo que la verdadera pregunta que me tengo que hacer es por qué tengo que implementar IEnumerable. Lo estoy haciendo porque la implementación central hace, ya que no tengo requisitos reales para iterar sobre mi cola. – Bronumski

+0

Lamentablemente, no puedo marcarlos como correctos, así que les daré un día para que limpien sus respuestas y marquen el más votado como correcto. – Bronumski

Respuesta

18

Los iteradores siempre deben ser idempotentes, es decir, no modifique la cola mientras la itera.

No hay garantías de que no haya dos iteraciones simultáneas ...


Editar para hacer frente a sus nuevas observaciones:

Cuando otro programador (como su propio futuro;)) viene a añadir características al código, que puede no suponer que los iteradores son de un solo uso . Pueden agregar una declaración de registro que enumera lo que está en la cola justo antes de usarlo (oops).

Otra cosa en la que acabo de pensar es que el depurador de Visual Studio generalmente enumerará tus clases para mostrar. Eso causaría algunos errores extremadamente confusos :)

Si está implementando una subinterfaz de IEnumerable, y no desea admitir IEnumerable, debe lanzar una NotSupportedException. Aunque esto no le dará advertencias de tiempo de compilación, el error de tiempo de ejecución será muy claro, mientras que una implementación extraña de IEnumerable podría perder horas futuras.

+0

Estoy de acuerdo en que los iteradores deberían ser idempotentes, solo me pregunto si es una fila donde se puede romper el molde. En mi caso (ver edición) no importaría si hubiera múltiples procesos iterando sobre la cola y de hecho probablemente lo haya. – Bronumski

+1

Es incluso más simple que un problema de simultaneidad. IEnumerables son solo de reenvío, de solo lectura. La modificación de la colección de hormigón subyacente puede hacer que las implementaciones de IEnumerable se "pierdan", por lo que la mayoría de ellas no lo permite. Tome una lista, por ejemplo. Conéctelo a un foreach que elimine cada elemento. En la primera ronda, el índice 0 desaparece y el índice 1 se convierte en el índice 0. Cuando el enumerador llama a MoveNext, intentará pasar del índice 0 al índice 1, que ahora es realmente el índice 2. Esto dará como resultado un comportamiento no deseado. – KeithS

+3

@ Keith, en general, es un punto excelente, pero en el código del OP, la implementación del enumerador no muestra ese problema en particular. –

0

Iterating a través de la cola debe NOT dequeue.

Esto está diseñado para ver examinar el contenido de la cola para no eliminarla. Así es también como funciona MSMQ o MQ Series.

0

No creo que debería. Sería muy implícito, y no comunica esa intención a nadie que esté acostumbrado a usar .NET Framework.

0

Yo diría que el segundo. Su enumerador definitivamente no debería cambiar el estado de la colección.

2

Yo diría no, hazlo de la segunda manera.

Esto no solo es más coherente con la clase incorporada Queue, sino que también es más coherente con el hecho de que la interfaz IEnumerable<T> está diseñada para ser de solo lectura.

Además, ¿esto realmente parecer intuitivo para disfrutar ?:

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

Acepto que si así es como iba a usarlo, usarlo es un poco de abuso y creo que la pregunta que realmente debería hacerme es si mi cola realmente implementa IEnumerable solo porque la cola central sí lo hace. Como no es un requisito, tendré que decir que no. – Bronumski

+0

@Bronumski: Como se trata de una sola línea de código, creo que la verdadera pregunta es, ¿por qué no? Definitivamente podría imaginar la capacidad de enumerar (o usar Linq especialmente) los elementos de una cola para que sean útiles, así que realmente no veo por qué no lo haría. –

+0

Seguro para una API básica, podría ver que es útil, pero cuando desarrolle desde un enfoque ágil y acepte que no voy a abusar del idioma, tendré que llamar a YAGNI. – Bronumski

0

tuve código en un encuentro propperty no se idempotente ... Fue un dolor para decodificar. Por favor, sigue con el manual Dequeue.

También es posible que no sea el único que trabaje en la cola, por lo que podría complicarse con más de un consumidor.

12

Absolutamente positivo, no hay forma de que deba mutar una colección a medida que la repite. Todo el punto de iteradores es que proporcionan una vista no destructiva de solo lectura de una colección. Sería profundamente sorprendente para cualquiera que use su código que al mirarlo lo cambie.

En particular: no desea examinar el estado de su cola en el depurador para cambiarlo. El depurador llama a IEnumerable al igual que cualquier otro consumidor, y si eso tiene efectos secundarios, se ejecutan.

+2

Punto justo, uno que no había considerado. – Bronumski

+0

Puede que le interese ver algunas de las respuestas a mi otra pregunta http://stackoverflow.com/questions/4194900/should-a-class-that-has-a-getenumerator-method-but-does-not-implement -ienumerable – Bronumski

+0

-1 ¿Por qué, entonces, el [BlockingCollection . GetConsumingEnumerable Method] (http://msdn.microsoft.com/en-us/library/vstudio/dd287186 (v = vs.100) .aspx) elimina y devolver artículos de la colección? Está mutando absolutamente positivamente la colección a medida que la repites. –

5

Sugeriría que le gustaría tener un método llamado DequeueAll que devuelve un elemento de una clase que cuenta con un método GetEnumerator que representa todo en la cola y borra la cola (si se agrega un elemento de cola alrededor el momento en que se crea iEnumerable, el nuevo elemento debe aparecer en el presente todo en AllItemsDequeued y no en la cola, o en la cola, pero no en la presente).Si esta clase implementa iEnumerable, se debe construir de tal manera que el objeto devuelto siga siendo válido incluso después de que se haya creado y eliminado un enumerador (lo que permite que se enumere varias veces). Si eso no fuera práctico, puede ser útil dar a la clase un nombre que sugiera que los objetos de la clase no deberían persistir. Todavía se podría hacer

foreach(QueueItem theItem in theQueue.DequeueAll()) {}
, pero sería menos probable que (erróneamente) guarde el resultado de Queue.DequeueAll en un iEnumerable. Si se quiere el máximo rendimiento y se permite el resultado de QueQueue.DequeueAll se use como un iEnumerable, se podría definir un molde de ampliación que tomaría una instantánea del resultado DequeueAll (permitiendo descartar los elementos antiguos).

+0

Me gusta eso, pensando fuera de la caja. – Bronumski

1

Estrictamente hablando, la cola proporciona solo push-back, pop-front, is-empty y quizás get-size operaciones. Todo lo que agrega junto a eso no es parte de una cola, por lo que si decide proporcionar operaciones adicionales, no necesita mantener la semántica de la cola.

En particular, los iteradores no son parte de la interfaz de cola estándar, por lo que no es necesario que eliminen el elemento iterativo actual. (Como otros han señalado, esto también contradiría las expectativas en los iteradores).

+0

Creo que eso era parte de mi problema, lo estaba mirando desde el punto de vista más puro, donde una cola solo debe presionar y abrir. Buena explicación. – Bronumski

3

Voy a dejar atrás el carro y digo . Esto parece un enfoque razonable. Aunque tengo que hacer una sugerencia. Es decir, no implemente este iterador consumidor en el método GetEnumerator. En su lugar llámalo GetConsumingEnumerator o algo similar. De esta forma, es obvio lo que sucederá y el mecanismo foreach no lo usará de forma predeterminada. No serás la primera persona en hacer esto. De hecho, Microsoft ya hace esto en el BCL a través de la clase BlockingCollection e incluso usaron GetConsumingEnumerator como el método . Creo que sabes lo que voy a sugerir la próxima ¿verdad?

¿Cómo cree que me ocurrió con el nombre sugerencia? ¿Por qué no usar BlockingCollection? Hace todo lo que ha pedido y más.

+0

Me había dado cuenta de la interfaz IProducerConsumerCollection antes, pero nunca me di cuenta de la BlockingCollection , gracias por el aviso. – Bronumski

Cuestiones relacionadas