¿Por qué IEnumerator < T > heredar de IDisposable mientras que el IEnumerator no genérico no lo hace?

StackOverflow https://stackoverflow.com/questions/232558

  •  04-07-2019
  •  | 
  •  

Pregunta

Noté que el IEnumerator < T > genérico se hereda de IDisposable, pero el IEnumerator no genérico no lo hace. ¿Por qué está diseñado de esta manera?

Generalmente, usamos la instrucción foreach para pasar por una instancia de IEnumerator < T > . El código generado de foreach en realidad tiene un bloque try-finally que invoca a Dispose () finalmente.

¿Fue útil?

Solución

Básicamente fue un descuido. En C # 1.0, foreach nunca se llama Dispose 1 . Con C # 1.2 (introducido en VS2003 - no hay 1.1, de manera extraña) foreach comenzó a verificar en el bloque finally si el iterador implementó IDisposable - tenían que hacerlo de esa manera, porque retrospectivamente haciendo que IEnumerator se extienda IDisposable habría roto la implementación de IEnumerator por todos. Si hubieran determinado que es útil para foreach desechar iteradores en primer lugar, estoy seguro de que IEnumerator habría ampliado IDisposable .

Sin embargo, cuando C # 2.0 y .NET 2.0 salieron a la luz, tuvieron una nueva oportunidad: una nueva interfaz, una nueva herencia. Tiene mucho más sentido que la interfaz extienda IDisposable para que no necesite una verificación del tiempo de ejecución en el bloque finally, y ahora el compilador sabe que si el iterador es un IEnumerator < ; T > puede emitir una llamada incondicional a Dispose .

EDITAR: es increíblemente útil para que se llame a Dispose al final de la iteración (sin embargo, termina). Significa que el iterador puede aferrarse a los recursos, lo que hace posible que, por ejemplo, lea un archivo línea por línea. Los bloques de iterador generan implementaciones de Dispose que aseguran que cualquier finally bloquee el punto actual de ejecución " " " Los iteradores se ejecutan cuando se eliminan, por lo que puede escribir un código normal dentro del iterador y la limpieza debe realizarse de manera adecuada.


1 Mirando hacia atrás en la especificación 1.0, ya estaba especificado. Todavía no he podido verificar esta declaración anterior de que la implementación 1.0 no llamó al Dispose .

Otros consejos

IEnumerable < T > No hereda IDisposable. IEnumerator < T > Sin embargo, hereda IDisposable, mientras que el IEnumerator no genérico no lo hace. Incluso cuando usa foreach para un IEnumerable no genérico (que devuelve IEnumerator), el compilador seguirá generando una verificación de IDisposable y llamará a Dispose () si el enumerador implementa la interfaz.

Supongo que el Enumerador genérico < T > se hereda de IDisposable, por lo que no es necesario que haya una verificación de tipo en tiempo de ejecución; puede seguir adelante y llamar a Dispose (), que debería tener un mejor rendimiento, ya que probablemente se puede optimizar si el enumerador tiene un método vacío de Dispose () .

Sé que esta es una discusión antigua, pero escribí una biblioteca donde usé IEnumerable de T / IEnumerator of T donde los usuarios de la biblioteca podrían implementar iteradores personalizados, solo deberían implementar IEnumerator de T.

Me pareció muy extraño que IEnumerator of T heredara de IDisposable. Implementamos IDisposable si queremos liberar recursos no modificados, ¿no? Por lo tanto, solo sería relevante para los enumeradores que realmente tienen recursos no administrados, como una secuencia de IO, etc. ¿Por qué no permitir que los usuarios implementen tanto IEnumerator de T como IDisposable en su enumerador si tiene sentido? En mi libro, esto viola el principio de responsabilidad única: por qué mezclar la lógica del enumerador y eliminar los objetos.

¿IEnumerable` hereda la IDisposing? De acuerdo con el reflector .NET o MSDN . ¿Está seguro de que no lo está confundiendo con IEnumerator ? Eso usa IDisposing porque solo es para enumerar una colección y no para la longevidad.

Es un poco difícil ser definitivo al respecto, a menos que logres obtener una respuesta del propio AndersH, o de alguien cercano a él.

Sin embargo, mi conjetura es que se relaciona con el " rendimiento " palabra clave que se introdujo en C # al mismo tiempo. Si observa el código generado por el compilador cuando " produce el rendimiento x " se usa, verá el método envuelto en una clase auxiliar que implementa IEnumerator; si IEnumerator desciende de IDisposable se asegura de que se pueda limpiar cuando se complete la enumeración.

IIRC Todo lo relacionado con tener IEnumerable < T > y IEnumerable es el resultado de IEnumerable que preceden a la plantilla de .Net. Sospecho que tu pregunta es de la misma manera.

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