2008-12-21 14 views
10

Noté que List<T> define su enumerador como struct, mientras que ArrayList define su enumerador como class. ¿Cual es la diferencia? Si voy a escribir un enumerador para mi clase, ¿cuál sería preferible?Implementación del enumerador: ¿usa struct o class?

EDITAR: Mis requisitos no pueden cumplirse usando yield, entonces estoy implementando un enumerador de los míos. Dicho esto, me pregunto si sería mejor seguir las líneas de List<T> e implementarlo como una estructura.

+1

Me interesaría mucho saber dónde aborda un problema utilizando el retorno de rendimiento. –

+0

1) Odio mucho la "magia del compilador", por lo que no estoy seguro de cuál será el resultado. (Aunque lo utilizaría para situaciones simples.) –

+0

2) Quiero admitir una lista que se puede modificar durante la iteración. La semántica normal del iterador prohíbe eso. (¡Y no sé cómo el compilador sabría que mi lista se modificó!) –

Respuesta

9

Como este, yo elegiría una clase. Las estructuras mutables son desagradables. (Y como sugiere Jared, que haría uso de un bloque de iterador. Mano de codificación de un enumerador es incómoda para hacerlo bien.)

Ver this thread para un ejemplo de la lista enumerador siendo problemas que causan una estructura mutable ...

+0

Bueno, ese ejemplo demostró que debería convertirlo en una clase. :) ¿Pero por qué se implementa List de esa manera? ¿Por qué no fue corregido? –

+0

Sin duda es demasiado tarde para cambiarlo ahora, pero no sé por qué fue diseñado de esa manera en primer lugar. Presumiblemente en nombre de la eficiencia, pero una mala decisión, IMO. –

+1

Supongo que era así que el estado actual de un enumerador podría 'salvarse' simplemente copiando el enumerador a otra variable - Recientemente he usado esta 'característica' para hacer divisiones de listas vinculadas (como LinkedList .Enumerator es también un struct) – thecoop

5

Escríbela usando yield return.

cuanto a por qué otro modo podrían elegir entre class o struct, si se hace un struct y luego éste se encajonado en cuanto se devuelve como una interfaz, por lo que hace de él un struct causas justas copia adicional a tener lugar. No puedo ver el punto de eso!

+3

Lo que obliga a la pregunta: ¿por qué la Lista usa un enumerador de estructuras? –

+0

Hasta que a alguien se le ocurra por qué es una ventaja, tendremos que suponer que el responsable de Microserf se volvió temporalmente loco. –

4

Un enumerador es intrínsecamente una estructura cambiante, ya que necesita actualizar el estado interno para pasar al siguiente valor en la colección original.

En mi opinión, las estructuras deberían ser inmutables, entonces usaría una clase.

8

La forma más fácil de escribir un enumerador en C# es con el patrón de "rendimiento devuelto". Por ejemplo.

public IEnumerator<int> Example() { 
    yield return 1; 
    yield return 2; 
} 

Este patrón generará todo el código enumerador bajo el capó. Esto toma la decisión de tus manos.

1

Para ampliar en @Earwicker: generalmente está mejor que no escribiendo un tipo de enumerador, y en su lugar usando yield return para que el compilador lo escriba por usted. Esto se debe a que hay una serie de sutilezas importantes que podrías perder si lo haces tú mismo.

Consulte la pregunta "What is the yield keyword used for in C#?" de SO para obtener más información sobre cómo usarlo.

también Raymond Chen tiene una serie de publicaciones en el blog ("The implementation of iterators in C# and its consequences": partes 1, 2, 3 y 4) que muestran cómo implementar un iterador correctamente sin yield return, que muestra lo compleja que es, y por qué debería simplemente usar yield return.

1

Hay un par de blogposts que cubren exactamente este problema. Básicamente, las estructuras del enumerador son una muy mala idea ...

+0

No habría nada más que tener la cosa utilizada por 'foreach' como una estructura, si hubiera una manera limpia de que no sea del mismo tipo que' IEnumerable .GetEnumerator'. Curiosamente, si no fuera por la inferencia de tipo variable, no importaría mucho si 'GetEnumerator' devolviera una estructura o clase, ya que al asignar el valor de retorno a' IEnumerator 'efectivamente lo convierte en un objeto de clase (aunque uno con algo -el método 'Equals' rotado). El problema es con 'var myEnumerator = thing.GetEnumerator();'. – supercat

5

La lista de motivos utiliza un enumerador de estructuras para evitar la generación de basura en las instrucciones de foros. Esta es una buena razón, especialmente si está programando para Compact Framework, porque CF no tiene GC generacional y CF generalmente se usa en hardware de bajo rendimiento donde puede conducir rápidamente a problemas de rendimiento.

Además, no creo que las estructuras mutables sean fuente de problemas en algunos ejemplos publicados, sino en programadores que no entienden bien cómo funcionan los tipos de valores.

+0

No puedo ver cómo un enumerador 'struct' evitaría la generación de basura. ¿No sería aún más probable que terminara con varias copias del mismo enumerador, a diferencia de un solo objeto enumerador si se tratara de una "clase"? ¿O los tipos de valor no necesitan ser recogidos basura? – stakx

+0

Los tipos de valor no se recogen como basura de la misma manera que los tipos de referencia. Los tipos de referencia viven en el montón, mientras que los tipos de valores viven en la pila. Así, mientras que los tipos de referencia se desasignan compactando el montón del GC (puede ser lento si tiene un montón grande y complejo); los tipos de valor son desasignados haciendo estallar la pila (muy rápido). – zigzag

+0

Por lo general, no necesita preocuparse por la generación de basura porque GC será lo suficientemente rápido y no será un cuello de botella de rendimiento. Pero en casos especiales, como el código de alta frecuencia, una gran cantidad de asignación en el montón puede causar problemas de rendimiento al hacer que el GC se ejecute con más frecuencia de lo habitual. (por ejemplo, asignando muchos objetos nuevos a cada fotograma en un juego) – zigzag

2

Cualquier implementación de IEnumerable<T> debe devolver una clase. Puede ser útil, por motivos de rendimiento, tener un método GetEnumerator que devuelva una estructura que proporcione los métodos necesarios para la enumeración pero no implemente IEnumerator<T>; este método debe ser diferente de IEnumerable<T>.GetEnumerator, que luego debe implementarse explícitamente.

El uso de este enfoque permitirá un mejor rendimiento cuando la clase se enumere utilizando un bucle foreach o "For Each" en C# o vb.net o cualquier contexto donde el código que está realizando la enumeración sepa que el enumerador es struct, pero evita las trampas que de otro modo se producirían cuando el enumerador se encasilla y pasa por valor.

+1

Vale la pena señalar que este nivel de ganancia de rendimiento rara vez es importante en la mayoría de las aplicaciones cotidianas. Los desarrolladores deberían usar un generador de perfiles para descubrir los puntos problemáticos reales antes de sumergirse en un enumerador de estructuras. Aún así, es una técnica válida, y la he usado un par de veces con buenos resultados. –

Cuestiones relacionadas