2008-10-24 10 views

Respuesta

67

Básicamente fue un descuido. En C# 1.0, foreachnunca llamado Dispose . Con C# 1.2 (introducido en VS2003 - no hay 1.1, extrañamente) foreach comenzó a verificar en el bloque finally si el iterador implementó IDisposable - tenían que hacerlo de esa manera, porque retrospectivamente hacer IEnumerator ampliar IDisposable habría roto la implementación de todos IEnumerator. Si hubieran descubierto que es útil para el foreach deshacerse de los iteradores en primer lugar, estoy seguro de que IEnumerator habría extendido IDisposable.

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

EDITAR: Es increíblemente útil llamar al Dispose al final de la iteración (como sea que termine).Significa que el iterador puede conservar los recursos, lo que hace que sea factible, por ejemplo, leer un archivo línea por línea. El iterador bloquea las implementaciones del generador Dispose que aseguran que cualquier bloque finally relevante para el "punto de ejecución actual" del iterador se ejecute cuando se elimine, de modo que pueda escribir código normal dentro del iterador y la limpieza se realice de forma adecuada.


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

+0

¿tengo que esperar que un 'IEnumerable.GetEnumerator' (no genérico) sea' IDisposable' también? – Shimmy

+1

@Shimmy: El código que acepta implementaciones arbitrarias de 'IEnumerable' no genérico está obligado a garantizar que cualquier objeto desechable devuelto por' GetEnumerator' será eliminado. Código que no debe considerarse roto. – supercat

+0

@supercat: Desde que escribí esta respuesta, descubrí que ya estaba en la especificación 1.0. No he logrado obtener una instalación de 1.0 para verificar si tenía razón o no sobre el comportamiento. Editaré –

-2

IIRC Todo sobre tener IEnumerable<T> y IEnumerable es un resultado de IEnumerable anterior a la plantilla de .Net cosas. Sospecho que tu pregunta es de la misma manera.

0

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

+0

IDisposing? Además, ¿te refieres a "No según ..."? –

+2

Le di un +1 para contrarrestar el -1, ya que su mensaje se publicó entre el momento en que el póster original especificó incorrectamente IEnumerable e IEnumerator y fue probablemente lo que provocó que el OP solucionara su pregunta. No obstante, dado que la pregunta se ha solucionado hace tiempo, también puede eliminar su "respuesta", ya que ciertamente ya no es aplicable. – supercat

4

IEnumerable < T> no hereda IDisposable. IEnumerator < T> 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 generará una verificación para IDisposable y llamará a Dispose() si el enumerador implementa la interfaz.

supongo que el enumerador genérica < T> hereda de IDisposable por lo que no tiene que ser un tiempo de ejecución de tipo de salida que puede seguir adelante y llamar a Dispose() que debe tener un mejor rendimiento ya que puede ser probable que sea optimizado si el enumerador tiene un método Dispose() vacío.

+0

Mejor respuesta que la mía :( –

0

Un poco difícil de ser definitivo en esto, a menos que logre obtener una respuesta del propio AndersH, o alguien cercano a él.

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

+0

rendimiento solo hace que el compilador genere código para una máquina de estado que no necesita deshacerse más allá de GC normal –

+0

@marxidad: Completamente incorrecto. Considere lo que sucede si se produce una instrucción "using" en un bloque de iteradores Vea http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx –

+0

@Jon: No es del todo incorrecto. Aunque IDisposable no es estrictamente necesario para los casos en los que se usa * no *, es más sencillo simplemente hacer todos enumeradores de nuevo estilo desechables y llame a Dispose() cada vez, por si acaso. –

3

Sé que esto es una vieja discusión pero reasontly escribí una biblioteca donde solía IEnumerable de T/T, donde IEnumerator de los usuarios de la biblioteca podrían implementar iteradores personalizados que sólo debe implementar IEnumerator de T.

encontré es muy extraño que IEnumerator of T herede de IDisposable. Implementamos IDisposable si queremos liberar recursos no manchados ¿no? Por lo tanto, solo sería relevante para los enumeradores que realmente tienen recursos no administrados, como una secuencia IO, etc. ¿Por qué no solo permite que los usuarios implementen tanto IEnumerator of 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.

+0

Si 'GetEnumerator' devuelve un objeto que requiere limpieza (por ejemplo, porque está leyendo líneas de datos de un archivo que deberán cerrarse), una entidad que sabe cuándo el enumerador ya no es necesario debe tener algún medio para transmitir esa información a alguna entidad que puede realizar la limpieza. 'IDisposable' se comporta hacia atrás con respecto al Principio de Sustitución de Liskov, ya que una fábrica que devuelve cosas que pueden requerir limpieza no puede sustituirse de manera segura por una que promete devolver cosas que no lo hacen, pero la sustitución inversa sería segura y * debería * ser legítimo – supercat

+0

También encontré 'IDisposable' en' IEnumerator 'para ser un poco confuso, me pareció útil comprobar cómo el [' Enumerator' of 'List ' implementó 'IDisposable'] (https://referencesource.microsoft. com/# mscorlib/system/collections/generic/list.cs, 1154) en la fuente .NET. Observe cómo el 'Enumerator'' struct' tiene un método 'Dispose' pero no contiene nada. ** Tenga en cuenta que este comportamiento 'IDisposable' de ninguna manera implica algo como" A 'List ' debería disponer de su 'Bitmap's en' foreach'! ** " – jrh

+0

Relacionado: [IEnumerator: ¿Es normal tener un Empty Dispose? método?] (http://stackoverflow.com/questions/3061612/ienumerator-is-it-normal-to-have-an-empty-dispose-method) (respuesta: sí, lo es) – jrh

Cuestiones relacionadas