6

Digamos que tiene 3 clases que implementan IDisposable - A, B y C. Las clases A y B son ambos dependientes de la clase C.¿Un ejemplo problemático del patrón IDisposable?

  1. ¿Sería correcto decir que las clases A y B de implementación típica de Desechar() sería:

    public void Dispose() 
    { 
        if (m_C != null) m_C.Dispose(); 
    } 
    
  2. Si hay una instancia de A y e instancia de B que comparten la misma instancia de C, ¿cómo superar el problema que desechar una instancia de un dañaría th e instancia de B?

  3. Addenda de último minuto: si en el punto 2 se trata de un contenedor DI que instancia todas las instancias, ¿quién es responsable de deshacerse de los objetos? ¿Es el contenedor en sí? ¿Cómo?

Gracias, urig

Respuesta

12

El patrón disponer se basa en la existencia de bien establecida "propietario" de un quién decide cuando el recurso debe desecharse.

Si A y B necesitan referirse a la misma instancia de C, solo uno de ellos debe actuar como "propietario".

Mientras que puede hacer lo que equivale al recuento de referencias, generalmente me parece mejor documentar quién es el "dueño" de qué. Por ejemplo, cuando crea un Bitmap con una secuencia, a partir de ese punto en el Bitmap posee la transmisión, y usted no debe deshacerse de ella. Esto puede causar algunos problemas, pero en última instancia es más simple que tratar de sacar a la luz el recuento de referencias.

+0

Hay una razón por la que Jon tiene 195k rep :) –

+0

Para aclarar, en mi caso, no A ni B son los propietarios distintos de C. Por ejemplo, imagine que C presiona datos en tiempo real y tanto A como B lo consumen. Un contenedor DI se encarga de crear instancias de C para A y para B, pero ¿quién está a cargo de su eliminación? – urig

+1

@urig: En esa situación, ¿cuándo querrías * desecharlo? Normalmente encuentro que hay formas de diseñar alrededor de esto que evitan el problema por completo, pero eso depende de la situación. –

4

Hacer un cheque nulo no será de gran ayuda si B dispone de C esto no va a actualizar la referencia de A.

Debe asegurarse de que solo una de las clases sea propietaria de C. Esta clase de propietario es la responsable de su eliminación.

En general, la clase que crea C debería ser la clase que la elimine.

0

Dos métodos que podría pensar serían:

  1. crear una colección padres dentro de C, y en el método dispose de A y B, quitar automática de la colección de los padres del niño. Luego, si el recuento de la recopilación principal es 0, deséchelo.
  2. Lazy carga una propiedad dentro de A y B para acceder a C. Realiza una comprobación nula en C, si algún otro objeto lo ha destruido, vuelve a instalarlo (si es posible).
0
  1. Normalmente lo hago de esta manera, tiende a ser aceptado y definitivamente funciona. Sin embargo, si otro objeto lo ha eliminado, la comprobación nula no dejará de deshacerse de que se vuelva a llamar porque no será nulo. Sin embargo, la disposición de C debe defenderse contra múltiples llamadas.

  2. Muy buena pregunta.Inmediatamente, lo que viene a la mente es que debe haber lógica para saber cuántos recuentos se encuentran actualmente en ese objeto, por lo que las rutinas propias de eliminación pueden defenderse. Los semáforos hacen esto, pero son pesados.

También me pregunto dónde podría ver esto en un ejemplo del mundo real, podría ser una discrepancia de diseño. Actualización: como han mencionado otros, se trata de un problema de diseño: obtienes un efecto similar cuando utilizas las CCW, otra persona libera el objeto COM subyacente donde otros usuarios aún pueden usarlo.

0

Eso sería una aplicación correcta: Sin embargo, es posible que desee guardar las referencias a todos los objetos en función de una instancia específica de C tanto en A y B, y tienen un cheque por esa lista para ser vacía (excepto por el momento disposición del objeto) en el método CDispose.

1

Solo una instancia debe ser el propietario, y es responsable de la eliminación. La instancia no propietaria debe obtener la referencia C usando una función como Adjuntar, y no debería eliminarla.

1

Quién creó la instancia? En general, este es el propietario y debe ser responsable de la eliminación de la instancia.

Es probable que tenga una clase "externa" que creó C y luego la pasó, directa o indirectamente, a A y B. Este es probablemente el candidato natural que tiene la responsabilidad del ciclo de vida de C y debería deshacerse de eso.

[Editar: en respuesta al comentario de OP] Parece que deberías echarle un vistazo al diseño aquí. ¿Esto apunta a que se necesita un refactor?

Tiene una clase C que necesita eliminar, que es utilizada por A y B; ¿Debería tener una clase que tenga la responsabilidad general de organizar C a A y B, en lugar de hacer que creen C desde el contenedor DI ellos mismos? O es C realmente más de un singleton. ¿Realmente necesita deshacerse?

Creo que todo lo que digo es que esto parece indicar un diseño que necesita un poco de cambio; tener otra mirada con un ojo crítico.

+0

Gracias por ayudarme. Ese es un buen punto. Fallé al mencionar esto originalmente, pero este es un escenario DI. La clase que ejemplifica A, B y C es un contenedor DI. ¿El contenedor DI es responsable de la eliminación? – urig

+0

No estoy seguro si es un problema de diseño. El escenario es legítimo: C es un proveedor de servicios, A es transitorio pero B es un singleton. El problema estaba en mi suposición de que quienquiera que use C debería eliminarlo. Eso es incorrecto. El patrón correcto, como usted señaló, es que quien creó C debe deshacerse de él. – urig

1

Última adición minutos - Si en el punto 2 es un contenedor DI que instancia todos los casos, que es responsable de eliminación de los objetos? ¿Es el contenedor ? ¿Cómo?

Sí, el contenedor posee los objetos IDisposable que crea. El contenedor elimina estos objetos cuando está dispuesto por sí mismo. Todos los contenedores DI deben hacer esto de manera predeterminada.

En ocasiones, el marco DI le ofrece una forma de apropiarse. Por ejemplo, en Autofac puede solicitar que se le inyecte un Owned<T>, y luego puede llamar al Owned<T>.Dispose() de manera segura cuando termine con el objeto. Esto es especialmente útil si está creando instancias de forma dinámica a través de una fábrica Func<Owned<T>> inyectada. Tenga en cuenta que dichas "instancias propiedad" no están destinadas a ser compartidas.

0

Además de lo que dijo Jon, el creador es el propietario y debe desechar el Desechable.

En este caso, es el contenedor, y el contenedor es responsable de desechar los componentes. No todos los componentes son compatibles con esto (o al menos no todos por completo). Castle Windsor does. También Autofac lo admite.

0

Cada objeto IDisposable debe tener un propietario. Si un recurso debe ser compartido entre múltiples usuarios, cualquiera de los cuales puede ser el último en usarlo, entonces cada usuario debe mantener una referencia a su propio contenedor. Los objetos contenedoras deben utilizar otros medios que no sean IDisposable para coordinar con un único objeto envoltorio interno creado en privado que luego llamará al Dispose en el recurso. El objeto contenedor interno no necesita usar IDisposable para la limpieza porque no está expuesto públicamente, y el hecho de que no tiene que usar IDisposable significa que puede usar un medio de limpieza que acomoda múltiples propietarios.

Cuestiones relacionadas