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;
}
A Bus
tiene a Driver
, Driver
tiene dos Shoe
s, cada uno Shoe
tiene a Shoelace
. Todo muy tonto.
Agregue un objeto IDisposable a Shoelace
Más tarde decido que alguna operación en el Shoelace
podría ser multiproceso, así que agrego un EventWaitHandle
para que los hilos se comuniquen. Entonces Shoelace
ahora se ve así:
class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
Implemento IDisposable en Cordón
Pero ahora FxCop de Microsoft se quejará: "Implemente IDisposable en 'Shoelace' porque crea miembros de los siguientes tipos de IDisposable: 'EventWaitHandle'".
Está bien, puedo aplicar IDisposable
en Shoelace
y mi clase se convierte en poco aseado 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 en Shoelace
sí mismo no tiene recursos no administrados, podría usar la implementación de disposición más simple sin necesidad de Dispose(bool)
Destructor y:
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 cómo se propaga IDisposable
Correcto, eso es todo arreglado. Pero ahora FxCop se quejará de que Shoe
crea un Shoelace
, por Shoe
lo que IDisposable
también debe ser .
Y Driver
crea Shoe
así Driver
debe ser IDisposable
. Y Bus
crea Driver
así Bus
debe ser IDisposable
y así sucesivamente.
De repente, mi pequeño cambio Shoelace
me está causando mucho trabajo y mi jefe se pregunta por qué tengo que pagar Bus
para hacer un cambio Shoelace
.
La pregunta
¿Cómo evita esta propagación IDisposable
, pero se asegura de que sus objetos no gestionados se eliminen correctamente?
fuente
Respuestas:
Realmente no puede "evitar" que se propague IDisposable. Algunas clases deben eliminarse, como
AutoResetEvent
, y la forma más eficiente es hacerlo en elDispose()
método para evitar la sobrecarga de los finalizadores. Pero este método debe llamarse de alguna manera, por lo que exactamente como en su ejemplo, las clases que encapsulan o contienen IDisposable deben eliminarlos, por lo que también deben ser desechables, etc. La única forma de evitarlo es:using
patrón)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 tampoco 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.
fuente
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 padre crea y posee esencialmente un objeto hijo 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 de hoja en su jerarquía de objetos. Esto no siempre es una tarea fácil, pero es un ejercicio interesante. Desde una perspectiva lógica, no hay razón para que un ShoeLace deba 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 WaitHandle a una asociación suelta a través de un mapa en el punto en que se utiliza WaitHandle, puede romper esta cadena.
fuente
Para evitar la
IDisposable
propagación, debe intentar encapsular el uso de un objeto desechable dentro de un único método. Intenta diseñar de maneraShoelace
diferente:El alcance del controlador de espera está limitado al
Tie
método, y la clase no necesita tener un campo desechable, por lo que no será necesario que sea desechable.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á laIDisposable
declaración? Si piensa en laShoelace
abstracción , rápidamente se da cuenta de que no debería estar contaminada por dependencias de infraestructura, comoIDisposable
.IDisposable
debe reservarse para clases cuya abstracción encapsule un recurso que requiera una limpieza determinista; es decir, para clases donde la disposición es parte de la abstracción .fuente
AutoResetEvent
se usa para comunicarse entre diferentes subprocesos que operan dentro de la misma clase, por lo que tiene que ser una variable miembro. No puede limitar su alcance a un método. (por ejemplo, imagine que un subproceso simplemente se queda esperando un poco de trabajo bloqueándolowaitHandle.WaitOne()
. El subproceso principal llama alshoelace.Tie()
método, que simplemente hacewaitHandle.Set()
ay regresa de inmediato).Esto es básicamente lo que sucede cuando mezclas Composición o Agregación con clases desechables. Como se mencionó, la primera salida sería refactorizar el waitHandle sin cordones.
Dicho esto, puede eliminar considerablemente el patrón Desechable cuando no tiene recursos no administrados. (Todavía estoy buscando una referencia oficial para esto).
Pero puede omitir el destructor y GC.SuppressFinalize (esto); y tal vez limpie un poco el virtual virtual Dispose (bool disposing).
fuente
Curiosamente, si
Driver
se define como anteriormente:Luego, cuando
Shoe
se realizaIDisposable
, FxCop (v1.36) no se queja de queDriver
también debería serIDisposable
.Sin embargo, si se define así:
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
Shoe
instancias todavía están siendo creadas porDriver
y todavía deben eliminarse de alguna manera.fuente
Shoe
constructores,shoes
¿quedaría en un estado difícil?No creo que haya una forma técnica de evitar que se propague IDisposable si mantiene su diseño tan estrechamente acoplado. Uno debería preguntarse si el diseño es correcto.
En su ejemplo, creo que tiene sentido que el zapato sea dueño del cordón, y tal vez, el conductor debería tener sus zapatos. Sin embargo, el autobús no debe ser dueño del conductor. Por lo general, los conductores de autobuses no siguen los autobuses hasta el 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:
Desafortunadamente, el nuevo diseño es más complicado, ya que requiere clases adicionales para crear instancias y eliminar instancias concretas de zapatos y conductores, pero esta complicación es inherente al problema que se está resolviendo. Lo bueno es que los autobuses ya no necesitan ser desechables simplemente con el fin de deshacerse de los cordones de los zapatos.
fuente
¿Qué tal usar la Inversión de Control?
fuente