2012-02-07 12 views
23

Según lo que he leído, se tomó una decisión de diseño para ciertos tipos de enumerador de colecciones para que sean estructuras mutables en lugar de tipos de referencia por motivos de rendimiento. List.Enumerator es el más conocido.¿Por qué las matrices C# usan un tipo de referencia para enumeración, pero la lista <T> usa una estructura mutable?

Estaba investigando un código antiguo que usa matrices y me sorprendió descubrir que las matrices C# devuelven el tipo SZGenericArrayEnumerator como su tipo de enumerador genérico, que es un tipo de referencia.

Me pregunto si alguien sabe por qué el iterador genérico de Array se implementó como un tipo de referencia cuando tantas otras colecciones de rendimiento crítico usaban estructuras mutables.

Respuesta

36

Según lo que he leído, se tomó una decisión de diseño para ciertos tipos de enumeradores de colecciones para que sean estructuras mutables en lugar de tipos de referencia por motivos de rendimiento.

Buena pregunta.

En primer lugar, estás en lo cierto. Aunque, en general, los tipos de valores mutables son un mal olor de código, en este caso están justificados:

  • La mutación se oculta casi por completo por el usuario.
  • Es muy poco probable que alguien use el enumerador de manera confusa.
  • El uso de un tipo de valor mutable en realidad resuelve un problema de rendimiento realista en un escenario extremadamente común.

Me pregunto si alguien sabe por qué iterador genérico de matriz se implementó como un tipo de referencia cuando tantas colecciones críticos otro rendimiento utilizan estructuras mutables en su lugar.

Porque si eres el tipo de persona que se preocupa por el rendimiento de enumerar una serie entonces ¿Por qué utiliza un enumerador en el primer lugar? Es una matriz por el amor del cielo; simplemente escriba un ciclo for que itere sobre sus índices como una persona normal y nunca asigne el enumerador. (O un foreach bucle; el compilador de C# volverá a escribir el bucle foreach en el circuito equivalente for si se sabe que la colección de bucle es una matriz.)

La única razón por la que le obtener un enumerador de una matriz en el El primer lugar es si lo pasa a un método que toma un IEnumerator<T>, en cuyo caso si el enumerador es una estructura, entonces va a estar encajándolo de todos modos. ¿Por qué tomar el gasto de hacer el tipo de valor y luego boxearlo? Simplemente haga que sea un tipo de referencia para comenzar.

+0

Dada la prevalencia (y potencia expresiva) de LINQ, es probable que los enumeradores se recuperen para matrices con bastante frecuencia. ejemplo, las clases de reflexión en arrays de retorno .NET para muchas propiedades/métodos - y usar LINQ w/reflection es bastante útil. Que yo sepa, la mayoría de los operadores de LINQ no realizan comprobaciones de casos especiales para determinar si el IEnumerable <> con el que están tratando es realmente una matriz. – LBushkin

+0

@LBushkin: Correcto. Si no está dispuesto a asumir el costo de la asignación de montón y luego recolectar un * enumerador * de basura, entonces también es probable que no esté dispuesto a asumir el costo de la asignación de montón y recolección de basura * la consulta *, y todos los objetos del enumerador que crea y todos los delegados que tienes que crear Sin mencionar todos los gastos generales de llamar a esa consulta (verificar argumentos para ser correctos, etc.). LINQ to objects es razonablemente eficiente, ¡pero ciertamente no fue diseñado para minimizar las asignaciones de montón! LINQ asigna memoria de montón por todo el lugar. –

+0

Otro factor, sospecho, es que el enumerador de la 'Lista' original fue diseñado en los días previos a los genéricos. La estructura de 'List.Enumerator' solo afecta a tres tipos de código: código que usa explícitamente el tipo' List.Enumerator', código que acepta un parámetro genérico restringido a 'IEnumerable', o código que usa' var' para declarar un variable que contendrá el enumerador de una lista. Si un programador va a especificar 'List.Enumerator', el programador debe saber lo que está haciendo. En cuanto a los otros dos tipos de código, no podrían existir cuando se diseñó 'List.Enumerator'. – supercat

2

Las matrices obtienen un tratamiento especial en el compilador de C#. Cuando utiliza foreach en ellos, el compilador lo traduce en un bucle for. Por lo tanto, no hay beneficio en el rendimiento al usar enumeradores struct.

List<T> por el contrario, es una clase simple sin ningún tratamiento especial, por lo que el uso de una estructura da como resultado un mejor rendimiento.

+0

¿no se ha compilado por completo en los bucles for? –

+1

@OskarKjellin no, los bucles 'foreach' normales se convierten en algo así como' while (enumerator.MoveNext()) {var element = enumerator.Current; ...} 'mientras que' foreach' hace un bucle sobre el índice de matrices en la matriz ('for (int i = 0; i CodesInChaos

+6

@OskarKjellin: Todos los bucles' foreach' y 'for' * se compilan finalmente en bucles' while'. El punto de CodeInChaos es que los bucles 'foreach' en las matrices son compilado en el bucle de estilo 'for (int i = 0; i

Cuestiones relacionadas