2009-10-28 5 views
5

He estado pensando en el método IEnumerator.Reset(). Leí en la documentación de MSDN que solo estaba allí para la interoperabilidad COM. Como un programador de C++ me parece como un IEnumerator que admite Reset es lo que yo llamaría forward iterator, mientras que un IEnumerator que no admite Reset es realmente un input iterator.¿Se beneficiaría C# de las distinciones entre tipos de enumeradores, como los iteradores de C++?

Así que la primera pregunta es: ¿es correcto este entendimiento?

La segunda parte de mi pregunta es, ¿sería beneficioso para C# si hubiera una distinción entre los iteradores de entrada y los iteradores directos (o "enumeradores" si lo prefiere)? ¿No ayudaría a eliminar la confusión entre los programadores, como la que se encuentra en este SO question about cloning iterators?

EDIT: aclaración en los iteradores de entrada y entrada. Un iterador de entrada solo garantiza que puede enumerar los miembros de una colección (o de una función de generador o una secuencia de entrada) solo una vez. Así es exactamente como funciona IEnumerator en C#. Si puede o no enumerar por segunda vez, está determinado por si se admite Reset o no. Un iterador directo, no tiene esta restricción. Puede enumerar a los miembros tantas veces como desee.

Algunos programadores de C# no reparan por qué un IEnumerator no se puede usar de manera confiable en un algoritmo multipaso. Considere el siguiente caso:

void PrintContents(IEnumerator<int> xs) 
{ 
    while (iter.MoveNext()) 
    Console.WriteLine(iter.Current); 
    iter.Reset(); 
    while (iter.MoveNext()) 
    Console.WriteLine(iter.Current); 
} 

Si llamamos PrintContents en este contexto, no hay problema:

List<int> ys = new List<int>() { 1, 2, 3 } 
PrintContents(ys.GetEnumerator()); 

Sin embargo mira el siguiente:

IEnumerable<int> GenerateInts() { 
    System.Random rnd = new System.Random(); 
    for (int i=0; i < 10; ++i) 
    yield return Rnd.Next(); 
} 

PrintContents(GenerateInts()); 

Si el IEnumerator apoyaron Reset, en otras palabras admitían algoritmos de paso múltiple, luego cada iteración sobre la colección sería diferente. Esto sería indeseable, porque sería un comportamiento sorprendente. Este ejemplo es un poco falso, pero ocurre en el mundo real (por ejemplo, leyendo flujos de archivos).

+1

Creo que te refieres a 'IEnumerator.Reset', no' IEnumerable .Reset', ¿verdad? –

+0

¡Sí, gracias! Lo siento por eso. – cdiggins

+0

Interesante pregunta. Pero tal vez deberías explicar el C++, hablar un poco, ya que muchos programadores de C# van a ver esto. Puede no ser obvio qué es un iterador de entrada y un iterador directo exactamente (en particular, las capacidades de uno/varios pasos de ellos, que son lo que realmente es relevante para esta pregunta) – jalf

Respuesta

2

Interesante pregunta. Mi opinión es que, por supuesto, C# sería beneficio. Sin embargo, no sería fácil de agregar.

La distinción existe en C++ debido a su sistema de tipo mucho más flexible. En C#, no tiene una forma genérica robusta de clonar objetos, que es necesaria para representar iteradores de ida (para admitir la iteración de múltiples pasos). Y, por supuesto, para que esto sea realmente útil, también deberá admitir iteradores/enumeradores bidireccionales y de acceso aleatorio. Y para que todos funcionen sin problemas, realmente necesitas algún tipo de pato-tipado, como lo han hecho las plantillas C++.

En última instancia, los alcances de los dos conceptos son diferentes.

En C++, se supone que los iteradores representan todo lo que necesita saber sobre un rango de valores. Dado un par de iteradores, no necesito el el contenedor original. Puedo ordenar, puedo buscar, puedo manipular y copiar elementos tanto como me gusta. El contenedor original está fuera de la imagen.

En C#, los enumeradores no deben hacer tanto. En última instancia, están diseñados para permitirle ejecutar la secuencia de forma lineal.

En cuanto a Reset(), es ampliamente aceptado que fue un error agregarlo en primer lugar. Si hubiera funcionado y se hubiera implementado correctamente, entonces sí, podría decirse que su enumerador era análogo a los iteradores de reenvío, pero, en general, es mejor ignorarlo como un error. Y luego todos los enumeradores son similares solo a los iteradores de entrada.

Desafortunadamente.

+0

Después de una taza de café, no puedo ver por qué la clonación es necesaria para un iterador de múltiples pasos (ignorando los requisitos completos de un iterador directo) por ahora. – cdiggins

+0

¿De qué otro modo haría múltiples pasadas en un iterador directo? No es bidireccional, por lo que no puede volver a donde estaba e iterar nuevamente. Tienes que crear una copia del iterador, por lo que tienes dos iteradores apuntando a la misma ubicación en la secuencia, y luego pueden incrementarse individualmente. – jalf

+0

Si está pensando que el método 'Reset()' podría reemplazar la clonación, sí, más o menos. Excepto que el reinicio solo lo regresa a un punto fijo en la secuencia (el comienzo). Pero un iterador directo debería poder hacer múltiples pasadas en un subconjunto de los datos (por ejemplo, los últimos tres elementos solamente). Restablecer realmente no ayuda allí. (o al menos, sería ridículamente lento si tuviera que hacer un reinicio total, y recorrer toda la secuencia solo para volver al lugar donde deseaba realizar el recorrido múltiple) – jalf

3

Reset fue un gran error. Llamo engaños al Reset. En mi opinión, la forma correcta de reflejar la distinción que está haciendo entre "iteradores de avance" e "iteradores de entrada" en el sistema de tipo .NET es con la distinción entre IEnumerable<T> y IEnumerator<T>.

Ver también this answer, donde Eric Lip Lip de Microsoft (sin ser oficial, sin dudas, mi punto es solo que es alguien con más credenciales de lo que tengo que decir que fue un error de diseño) hace un punto similar en comentarios También vea también his awesome blog.

+1

+1 para los enlaces. Sin embargo, no estoy de acuerdo con tu analogía. IEnumerable es más análogo a un contenedor C++ http://www.sgi.com/tech/stl/Container.html – cdiggins

-1

No lo creo. Llamaría al IEnumerable un iterador directo, y un iterador de entrada. No le permite retroceder ni modificar la colección subyacente. Con la adición de la palabra clave foreach, los iteradores casi no piensan la mayor parte del tiempo.

Opinión: La diferencia entre los iteradores de entrada (obtener cada uno) vs. iteradores de salida (hacen algo para cada uno) es demasiado trivial para justificar una adición al marco.Además, para hacer un iterador de salida, necesitaría pasar un delegado al iterador. El iterador de entrada parece más natural para los programadores de C#.

También hay IList<T> si el programador desea acceso aleatorio.

+0

En C++, un iterador directo admite algoritmos de pasadas múltiples, esto no es algo que se pueda hacer de manera confiable con un IEnumerable/IEnumerator. Por ejemplo: si el IEnumerable se generó a partir de una función de rendimiento. – cdiggins

+0

No entiendo por qué IEnumerable no es compatible con los algoritmos de paso múltiple. Un iterador personalizado (que usa retorno de rendimiento) crea una instancia de IEnumerator. Puede tener varios objetos IEnumerator iterando sobre la misma colección a la vez. –

+2

Pero dado solo un IEnumerator, no puede hacer más de un pase sobre la colección. Necesita tener acceso a la colección subyacente para realizar la iteración multipaso, lo cual es un inconveniente. (Como en C++, los iteradores están diseñados para cubrir todas sus necesidades iterativas. En C#, algunos algoritmos bastante básicos (cualquier cosa que requiera más de un pase) tienen que romper la abstracción y requerir acceso al contenedor subyacente) – jalf

1

Desde el punto de vista de C#:

Casi nunca se utiliza IEnumerator directamente. Por lo general, usted hace una declaración foreach, que espera un IEnumerable.

IEnumerable _myCollection; 
... 
foreach (var item in _myCollection) { /* Do something */ } 

Usted no pasa alrededor IEnumerator tampoco. Si desea pasar una colección que necesita iteración, pase IEnumerable. Como IEnumerable tiene una función única, que devuelve IEnumerator, se puede usar para iterar la colección varias veces (múltiples pasadas).

No hay necesidad de una función Reset() en IEnumerator porque si desea comenzar de nuevo, simplemente tire la anterior (basura recolectada) y obtenga una nueva.

1

El .NET framework se beneficiaría inmensamente si hubiera un medio de preguntar IEnumerator<T> sobre qué capacidades podría soportar y qué podría hacer. Dichas características también serían útiles en IEnumerable<T>, pero poder formular las preguntas de un enumerador permitiría que el código que puede recibir un enumerador de contenedores como ReadOnlyCollection pueda usar la colección subyacente de mejor manera sin tener que involucrar al contenedor.

Dado que cualquier enumerador de una colección que se puede enumerar en su totalidad y que no es demasiado grande, se podría producir un IEnumerable<T> que siempre arrojaría la misma secuencia de elementos (específicamente el conjunto de elementos restantes en el enumerador) leyendo todo su contenido en una matriz, eliminando y desechando el enumerador, y obteniendo un enumerador de la matriz (usando eso en lugar del enumerador abandonado original), envolviendo la matriz en un ReadOnlyCollection<T> y devolviéndola. Aunque tal enfoque funcionaría con cualquier tipo de colección enumerable que cumpla con los criterios anteriores, sería terriblemente ineficiente con la mayoría de ellos. Disponer de un medio para pedirle a un enumerador que proporcione los contenidos restantes en un IEnumerable<T> inmutable permitiría a muchos tipos de enumeradores realizar la acción indicada de manera mucho más eficiente.

Cuestiones relacionadas