example c# .net garbage-collection dispose idisposable

c# - example - ¿Cómo previene que IDisposable se propague a todas sus clases?



idisposable implementation example c# (8)

¿Qué le parece usar Inversion of Control?

class Bus { private Driver busDriver; public Bus(Driver busDriver) { this.busDriver = busDriver; } } class Driver { private Shoe[] shoes; public Driver(Shoe[] shoes) { this.shoes = shoes; } } class Shoe { private Shoelace lace; public Shoe(Shoelace lace) { this.lace = lace; } } class Shoelace { bool tied; private AutoResetEvent waitHandle; public Shoelace(bool tied, AutoResetEvent waitHandle) { this.tied = tied; this.waitHandle = waitHandle; } } class Program { static void Main(string[] args) { using (var leftShoeWaitHandle = new AutoResetEvent(false)) using (var rightShoeWaitHandle = new AutoResetEvent(false)) { var bus = new Bus(new Driver(new[] {new Shoe(new Shoelace(false, leftShoeWaitHandle)),new Shoe(new Shoelace(false, rightShoeWaitHandle))})); } } }

Comience con estas clases simples ...

Digamos que tengo un conjunto simple de clases como esta:

class Bus { Driver busDriver = new Driver(); } class Driver { Shoe[] shoes = { new Shoe(), new Shoe() }; } class Shoe { Shoelace lace = new Shoelace(); } class Shoelace { bool tied = false; }

Un Bus tiene un Driver , el Driver tiene dos Shoe , cada Shoe tiene un Shoelace . Todo muy tonto.

Agregue un objeto IDisposable a Shoelace

Más tarde decido que alguna operación en el Shoelace podría ser de múltiples hilos, por lo que agrego un EventWaitHandle para que los hilos se comuniquen. Así que Shoelace ahora se ve así:

class Shoelace { private AutoResetEvent waitHandle = new AutoResetEvent(false); bool tied = false; // ... other stuff .. }

Implementar IDisposable en cordones de los zapatos

Pero ahora FxCop de Microsoft se quejará: "Implementar IDisposable en ''Shoelace'' porque crea miembros de los siguientes tipos IDisposable: ''EventWaitHandle''."

De acuerdo, implemento un IDisposable en IDisposable y mi pequeña y ordenada clase se convierte en este horrible desastre:

class Shoelace : IDisposable { private AutoResetEvent waitHandle = new AutoResetEvent(false); bool tied = false; private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~Shoelace() { Dispose(false); } protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { if (waitHandle != null) { waitHandle.Close(); waitHandle = null; } } // No unmanaged resources to release otherwise they''d go here. } disposed = true; } }

O (como lo señalaron los comentaristas), ya que Shoelace no tiene recursos no administrados, podría usar la implementación de disposición más simple sin necesidad del Dispose(bool) y Destructor:

class Shoelace : IDisposable { private AutoResetEvent waitHandle = new AutoResetEvent(false); bool tied = false; public void Dispose() { if (waitHandle != null) { waitHandle.Close(); waitHandle = null; } GC.SuppressFinalize(this); } }

Mire con horror como diferenciales IDisposable

Correcto, eso es todo arreglado. Pero ahora FxCop se quejará de que Shoe crea un Shoelace , por lo que el Shoe debe ser IDisposable .

Y el Driver crea Shoe entonces el Driver debe ser IDisposable . Y el Bus crea el Driver así que el Bus debe ser IDisposable y así sucesivamente.

De repente, mi pequeño cambio a Shoelace me está causando mucho trabajo y mi jefe se pregunta por qué tengo que pagar Bus para hacer un cambio en Shoelace .

La pregunta

¿Cómo se evita esta dispersión de IDisposable , pero aún así se asegura de que sus objetos no administrados se eliminen adecuadamente?


Curiosamente, si Driver se define como arriba:

class Driver { Shoe[] shoes = { new Shoe(), new Shoe() }; }

Luego, cuando Shoe se hace IDisposable , FxCop (v1.36) no se queja de que Driver también debe ser IDisposable .

Sin embargo, si se define así:

class Driver { Shoe leftShoe = new Shoe(); Shoe rightShoe = new Shoe(); }

entonces se quejará.

Sospecho que esto es solo una limitación de FxCop, en lugar de una solución, porque en la primera versión las instancias Shoe todavía están siendo creadas por el Driver y todavía deben eliminarse de alguna manera.


En términos de corrección, no puede evitar la propagación de IDisposable a través de una relación de objeto si un objeto principal crea y esencialmente posee un objeto secundario que ahora debe ser desechable. FxCop es correcto en esta situación y el padre debe ser IDisposable.

Lo que puede hacer es evitar agregar un IDisposable a una clase hoja en su jerarquía de objetos. Esta no es siempre una tarea fácil, pero es un ejercicio interesante. Desde una perspectiva lógica, no hay ninguna razón por la que ShoeLace necesite ser desechable. En lugar de agregar un WaitHandle aquí, también es posible agregar una asociación entre un ShoeLace y un WaitHandle en el punto en que se usa. La forma más simple es a través de una instancia de diccionario.

Si puede mover el WaitHandle en una asociación suelta a través de un mapa en el punto en que realmente se usa WaitHandle, puede romper esta cadena.


Esto es básicamente lo que sucede cuando mezclas Composición o Agregación con clases Desechables. Como se mencionó anteriormente, la primera salida sería refactorizar el waitHandle fuera del cordón.

Habiendo dicho eso, puedes quitar el patrón Desechable considerablemente cuando no tienes recursos no administrados. (Todavía estoy buscando una referencia oficial para esto).

Pero puede omitir el destructor y GC.SuppressFinalize (this); y tal vez limpiar el vacío virtual Dispose (bool disposing) un poco.


Esto se parece mucho a un problema de diseño de alto nivel, como suele ser el caso cuando una "solución rápida" se convierte en un atolladero. Para obtener más información sobre las salidas, es posible que este tema le resulte útil.


No creo que exista una forma técnica de prevenir la propagación de IDisposable si mantiene su diseño tan estrechamente unido. Uno debería preguntarse si el diseño es correcto.

En su ejemplo, creo que tiene sentido que el zapato tenga el cordón del zapato, y tal vez, el conductor debe poseer sus zapatos. Sin embargo, el autobús no debe ser el propietario del conductor. Por lo general, los conductores de autobuses no siguen los autobuses al depósito de chatarra :) En el caso de los conductores y los zapatos, los conductores rara vez hacen sus propios zapatos, lo que significa que realmente no los "poseen".

Un diseño alternativo podría ser:

class Bus { IDriver busDriver = null; public void SetDriver(IDriver d) { busDriver = d; } } class Driver : IDriver { IShoePair shoes = null; public void PutShoesOn(IShoePair p) { shoes = p; } } class ShoePairWithDisposableLaces : IShoePair, IDisposable { Shoelace lace = new Shoelace(); } class Shoelace : IDisposable { ... }

Desafortunadamente, el nuevo diseño es más complicado, ya que requiere clases adicionales para instanciar y eliminar instancias concretas de zapatos y controladores, pero esta complicación es inherente al problema que se está resolviendo. Lo bueno es que los autobuses ya no deben ser desechables simplemente con el fin de deshacerse de los cordones de los zapatos.


Para evitar que el IDisposable propague, debe intentar encapsular el uso de un objeto desechable dentro de un único método. Intente diseñar el Shoelace diferente:

class Shoelace { bool tied = false; public void Tie() { using (var waitHandle = new AutoResetEvent(false)) { // you can even pass the disposable to other methods OtherMethod(waitHandle); // or hold it in a field (but FxCop will complain that your class is not disposable), // as long as you take control of its lifecycle _waitHandle = waitHandle; OtherMethodThatUsesTheWaitHandleFromTheField(); } } }

El alcance del identificador de espera se limita al método Tie , y la clase no necesita tener un campo desechable, por lo que no será necesario desecharlo.

Dado que el identificador de espera es un detalle de implementación dentro del Shoelace , no debe cambiar de ninguna manera su interfaz pública, como agregar una nueva interfaz en su declaración. ¿Qué sucederá entonces cuando ya no necesite un campo desechable, eliminará la declaración IDisposable ? Si piensas en la abstracción de Shoelace , te das cuenta rápidamente de que no debe estar contaminada por dependencias de infraestructura, como IDisposable . IDisposable debe reservarse para las clases cuya abstracción encapsula un recurso que requiere una limpieza determinista; es decir, para las clases donde la desechabilidad es parte de la abstracción .


Realmente no se puede "evitar" que IDisposable se propague. Algunas clases deben eliminarse, como AutoResetEvent , y la forma más eficiente es hacerlo en el método Dispose() para evitar la sobrecarga de los finalizadores. Pero este método debe llamarse de alguna manera, así que, exactamente como en su ejemplo, las clases que encapsulan o contienen IDisposable tienen que eliminarlas, por lo que deben ser desechables, etc. La única forma de evitarlo es:

  • evite el uso de clases IDisposable cuando sea posible, bloquee o espere los eventos en un solo lugar, mantenga los recursos caros en un solo lugar, etc.
  • créelos solo cuando los necesites y deséchalos justo después (el patrón de using )

En algunos casos, IDisposable se puede ignorar porque admite un caso opcional. Por ejemplo, WaitHandle implementa IDisposable para admitir un Mutex con nombre. Si no se usa un nombre, el método Dispose no hace nada. MemoryStream es otro ejemplo, no utiliza recursos del sistema y su implementación Dispose también no hace nada. Pensar cuidadosamente si un recurso no administrado se está utilizando o no puede ser instructivo. También puede examinar las fuentes disponibles para las bibliotecas .net o usar un descompilador.