c# - ¿Por qué el enumerador generado por el compilador para "rendimiento" no es una estructura?
heap ienumerable (2)
La implementación generada por compilador de IEnumerator
/ IEnumerable
para métodos de yield
y getters parece ser una clase y, por lo tanto, se asigna en el montón. Sin embargo, otros tipos de .NET como List<T>
devuelven específicamente enumeradores de struct
para evitar la asignación de memoria inútil. A partir de una descripción general rápida de la publicación C # In Depth , no veo ninguna razón por la cual ese no sea el caso aquí.
¿Me estoy perdiendo de algo?
Esa clase solo se usará a través de la interfaz. Si fuera una estructura, se encasillaría el 100% del tiempo, lo que lo haría menos eficiente que usar una clase.
No puede no encasillarlo, ya que, por definición, es imposible usar el tipo en tiempo de compilación, ya que no existe cuando comienza a compilar el código.
Al escribir una implementación personalizada de IEnumerator
, puede exponer el tipo subyacente real antes de compilar el código, lo que permite que se use potencialmente sin estar encuadrado.
Servy contestó correctamente su pregunta, una pregunta que usted mismo respondió en un comentario:
Me acabo de dar cuenta de que, dado que el tipo de devolución es una interfaz, de todos modos quedaría encasillado, ¿verdad?
Derecha. Su pregunta de seguimiento es:
¿No se pudo cambiar el método para devolver un enumerador explícitamente tipado (como lo hace
List<T>
)?
Entonces su idea aquí es que el usuario escribe:
IEnumerable<int> Blah() ...
y el compilador en realidad genera un método que devuelve BlahEnumerable
que es una estructura que implementa IEnumerable<int>
, pero con los métodos y propiedades GetEnumerator
apropiados que permiten que la característica "coincidencia de patrón" de foreach
elide el boxeo.
Aunque es una idea plausible, existen serias dificultades cuando comienza a mentir sobre el tipo de devolución de un método. Particularmente cuando la mentira implica cambiar si el método devuelve una estructura o un tipo de referencia. Piensa en todas las cosas que van mal:
Supongamos que el método es virtual. ¿Cómo puede ser anulado? El tipo de devolución de un método de anulación virtual debe coincidir exactamente con el método anulado. (Y de forma similar para: el método anula otro método, el método implementa un método de una interfaz, etc.)
Supongamos que el método se convierte en un delegado
Func<IEnumerable<int>>
.Func<T>
es covariante enT
, pero la covarianza solo se aplica a los argumentos de tipo de tipo de referencia. El código parece que devuelve unIEnumerable<T>
pero, de hecho, devuelve un tipo de valor que no es covariancia compatible conIEnumerable<T>
, solo asignación compatible .Supongamos que tenemos
void M<T>(T t) where T : class
y llamamosM(Blah())
. Esperamos deducir queT
esIEnumerable<int>
, que pasa la comprobación de restricciones, pero el tipo de estructura no pasa la comprobación de restricciones.
Y así. Rápidamente terminas en un episodio de Three''s Company (chico, estoy saliendo conmigo mismo) donde una pequeña mentira termina convirtiéndose en un gran desastre. Todo esto para ahorrar una pequeña cantidad de presión de recolección. No vale la pena.
Sin embargo, observo que la implementación creada por el compilador ahorra en la presión de la colección de una manera interesante. La primera vez que se llama a GetEnumerator
en el enumerable devuelto, el enumerable se convierte en un enumerador. La segunda vez, por supuesto, el estado es diferente, por lo que asigna un nuevo objeto. Dado que el escenario probable del 99,99% es que una secuencia dada se enumera exactamente una vez, esto representa un gran ahorro en la presión de recolección.