Pregunta

Esta pregunta alcancía espaldas de otra pregunta los que me crié con respecto a abusar de la interfaz IEnumerable mediante la modificación de un objeto a medida que iterar sobre ella.

El consenso general es que no hay nada que implementa IEnumerable debe ser idempotente. Pero .NET admite la tipificación de pato tiempo de compilación con la instrucción foreach. Cualquier objeto que proporciona un método IEnumerator GetEnumerator () se puede utilizar dentro de una sentencia foreach.

Así que si el método GetEnumerator idempotente o se trata cuando se implementa IEnumerable?

EDITAR (contexto Añadido)

Para poner un poco de contexto alrededor de esto lo que estoy sugiriendo es que cuando la iteración en una cola se quita de la cola cada elemento a medida que avanza. Además los nuevos objetos inserta en la cola después de la llamada a GetEnumerator todavía se repiten a lo largo.

¿Fue útil?

Solución

No es el type que es idempotente - que ni siquiera tiene mucho sentido; que puede inmutables media, pero eso no está claro. Es el método GetEnumerator en sí, que es típicamente idempotente.

Mientras yo diría que es general el caso, puedo imaginar casos especiales en los que tiene sentido tener un método GetEnumerator no idempotente. Por ejemplo, podría ser que usted tiene datos que sólo pueden ser leídos una vez (porque está streaming desde un servidor web que no dará servicio a la misma petición de nuevo, o algo por el estilo). En ese caso, tendría GetEnumerator para invalidar de manera efectiva la fuente de datos, de modo que las futuras llamadas serían una excepción.

Estos tipos y métodos se debe documentar con mucho cuidado, por supuesto, pero creo que son razonables.

Otros consejos

This discussion is an old one and to my knowledge there's no common consensus.

Please do not confuse the concept of (runtime) Duck-Typing with abusing the compiler supported foreach to support your desired semantics.

Another concept you seem to confuse is Idempotence vs. Immutability. According to your wording you try to describe the second, which means the object providing the enumerator gets modified during enumeration. Idempotence on the other hand means your enumerator, when called twice will yield the same results.

Now that we're clear on this, you need to carefully decide on the semantics your IEnumerable operation should support. Certain kind of enumerations are hard to make idempotent (i.e. involve caching), and do usually fall into one of the following categories:

  • Enumerating over randomly changing data (i.e. a random number generator, sensor streams)
  • Enumerating over shared state (e.g. files, databases, streams etc.)

On the other hand, this only accounts for "source" operations. If you are implementing filter or transformation operations using enumerators, you should always try to make them idempotent.

It seems you want a queue class from which you can dequeue all items in a nice one-liner.

There's nothing wrong with this idea per se; I'd just question your preference to specifically use GetEnumerator to achieve what you're after.

Why not simply write a method that is more explicit in terms of what it does? For example, DequeueAll, or something of the sort.

Example:

// Just a simplistic example. Not the way I'd actually write it.
class CustomQueue<T> : Queue<T>
{
    public IEnumerable<T> DequeueAll()
    {
        while (Count > 0)
        {
            yield return Dequeue();
        }
    }
}

(Note that the above could even be an extension method, if it represents literally the only functionality you'd want above and beyond what is already provided by Queue<T>.)

This way you could still get the "clean"-looking code I suspect you're after, without the (potential) confusion of a non-idempotent GetEnumerator:

// Pretty clean, right?
foreach (T item in queue.DequeueAll())
{
    Console.WriteLine(item);
}

I would suggest that using ForEach on a collection shouldn't change it unless the name of the collection type implies that's going to happen. The issue in my mind would be what should be returned if a method is performed to consume a collection into something enumerable (e.g. to allow "For Each Foo in MyThing.DequeueAsEnum"). If DequeueAsEnum returns an iEnumerable, then someone could expect to get away with "Dim myIEnumerable As IEnumerable = MyThing.DequeueAsEnum" and then use MyIEnumerable in two disjoint For-Each loops. If DequeueAsEnum returns a type EnumerableOnlyOnce, then it would be a little clearer that its return should only be enumerated once. To be sure, the existence of implicit typing in newer C# and VB.Net dialects makes it a bit more likely that someone might assign the function return to a variable when they shouldn't, but I don't know how to prevent that.

BTW, there are a number of circumstances where it would be helpful to prevent a class reference from being stored into a variable; is there any way to declare a class in such a way that outside code can use expressions of that class type, but cannot declare variables of it?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top