2011-08-30 8 views
7

Usted está diseñando una interfaz IFooEl diseño de una interfaz donde sólo algunas implementaciones requieren IDisposable

public interface IFoo 
{ 
    void Bar(); 
} 

Digamos que hay cinco implementaciones de esta interfaz. Dos de esas implementaciones también deberían implementar IDisposable ya que usan recursos no administrados. Desde la perspectiva de un llamante, sería más fácil si IFoo implementara IDisposable para que cualquier IFoo se pueda envolver en un bloque using, pero, por supuesto, algunas de las implementaciones se llenarían con los métodos vacíos Dispose(). Simplemente curioso, ¿hay otras formas de hacer esto?

EDITAR

Su claramente mejor tener IFoo implemento IDisposable tomar cualquier responsabilidad de distancia desde el cliente. ¿Cómo sabrían cuándo y cuándo no verificar si es posible encontrar algo? Tendrían que hacer esto con todo lo demás. Todos parecen estar de acuerdo en que unos pocos métodos vacíos de Dispose() no dañarán a nadie.

Lo mejor que se puede sacar de esta pregunta es cómo verificar concisamente si algo puede desecharse con el "como" y "utilizando" en la respuesta de Marc Gravell.

+4

Si la persona que llama solo conoce el 'IFoo', y algunas implementaciones de' IFoo' deben eliminarse, 'IFoo' debe heredar' IDisposable'. – Dyppl

+0

Sí, lo mencioné en mi pregunta, era simplemente curioso sobre cualquier otro enfoque al problema para evitar implementaciones redundantes de IDisposable en clases concretas que no lo necesitaban. –

+0

la única otra opción es verificar si el objeto implementa 'IDisposable' en el lado de la persona que llama, vea mi respuesta. – Dyppl

Respuesta

7

sospecho que simplemente había demando IDisposable() - un no-op Dispose() no es una gran sobrecarga.

Si no se puede estar seguro de si es desechable, lo que sigue es bastante efectiva:

var mightBeDisposable = GetBlah(); 
using(mightBeDisposable as IDisposable) 
{ 
    // etc 
} 
+0

Porque si "as" falla, devuelve null en lugar de arrojar una excepción, por lo que siempre ingresará al bloque de uso ¿no? –

+0

@ Peter correcto; y 'using' ya incluye (en la especificación) una verificación' null', por lo que solo intenta llamar a '.Dispose()' en el caso de que 'mightBeDisposable' resulte ser' IDisposable' (y por lo tanto un valor no nulo) se observó) –

+0

Entendido, sección 8.13 "if (resource! = null) (IDisposable) resource) .Dispose();" –

1

Se puede crear una versión disponible de su interfaz que mejor muestra su intención:

public interface IDisposableFoo : IFoo, IDisposable 
{ 
} 

Cualquier clase que hereda esta interfaz todavía podrían ser tratados como un IFoo. Un problema que puede tener es la necesidad de verificar si su objeto IFoo es una versión desechable antes de tratar como tal, sin embargo, esto es bastante fácil.

+0

Las implementaciones de esta interfaz que no requieren IDisposable todavía necesitarán implementarlo. –

+0

@ Peter Kelly: luego use la interfaz IFoo para esas implementaciones. Esta opción es solo una forma de mostrar explícitamente en el código que hay una opción desechable –

0

Necesita 2 interfaces, una que implemente IDisposable y otra que no lo haga - IFoo y IDisposableFoo o simplemente haga que IFoo sea desechable.

No hay daño en tener algunas implementaciones vacías de IDisposable. Dado que ya tiene algunos que requieren desechar, parece un caso de uso probable.

1

Si no le importa añadir algo de código a su sitio de llamada, sólo puede hacer lo siguiente:

IFoo foo = GetMeSomeFoo(); 

foo.UseFoo(); 

var disposableFoo = foo as IDisposable; 
if (disposableFoo != null) 
    disposableFoo.Dispose(); 

No es bonita, pero no contamina su interfaz. Tampoco garantiza que la persona que llama hará todas estas cosas.

EDIT: como Hans Passant señaló, es esencialmente igual a

IFoo foo = GetMeSomeFoo(); 
using (foo as IDisposable) { 
    foo.UseFoo(); 
} 
+0

El problema con este enfoque es que no está usando la palabra clave que usa –

+0

@Hans Passant: limpio, no sabía que 'using' se comporta de esa manera con valores 'null', gracias – Dyppl

1

yo no forzar IFoo para implementar IDisposable, ya que está en contra de sólido. Puede derivar un IDisposableFoo de IFoo si lo desea o puede hacer un chequeo (o incluso un método personalizado que ajuste un IFoo en un Aparato de desechable desechable y comprueba si es IDisposable) si lo necesita.

class DisposableAdapter : IDisposable, IFoo 
{ 
    IFoo _obj; 
    public DisposableAdapter(IFoo obj) 
    { 
     _obj = obj; 
    } 

    public void Dispose() 
    { 
     if (_obj is IDisposable) 
     ((IDisposable)obj).Dispose(); 
    }  

    // copy IFoos implementations from obj 

} 

usando

using(var foo = new DisposableAdapter(myFoo)) //... use foo just as you had myFoo 
+2

¿Qué guía SOLID argumenta que viola? –

+0

Discutiría sobre alguna combinación del SRP y el ISP: el tío Bob lo llama "Contaminación de la interfaz". ¿Por qué obligar a algunos Foos a implementar métodos vacíos? – Carsten

3

Existen precedentes en el .NET Framework para las interfaces que implementan IDisposable - por ejemplo IComponent, IDataReader.

Parece un patrón razonable cuando espera que la mayoría de las implementaciones requieran eliminación.

+0

Buen punto. Expectativas del usuario –

2

Aunque los objetos que implementan interfaces generalmente prometen capacidades o características que no son comunes en objetos sin esas interfaces, y aunque IDisposable es una interfaz, el hecho de que un objeto implemente IDisposable no promete ninguna habilidad o característica que carecería en objetos que no. En cambio, el hecho de que un objeto implemente IDisposable implica que carece de una característica común en los objetos que no la implementan: la capacidad de cualquiera que adquiera o posea una referencia la abandonará sin importar si el objeto puede o debe limpiarse arriba primero

Si el código que utiliza un tipo de interfaz en particular generalmente no será lo último para mantener una referencia, entonces no hay necesidad de que la interfaz implemente IDisposable. Incluso si algunas implementaciones no pueden abandonarse de manera segura, eso no afectará a ningún usuario de una instancia que no sea la última que contiene una referencia. Si ese usuario generalmente va a saber más sobre el tipo específico de lo que implica la interfaz, el usuario sabrá si el objeto necesita limpieza, ya sea que la interfaz lo indique o no.

Por otro lado, si el último usuario de un objeto generalmente no sabrá nada más que el hecho de que implementa alguna interfaz, esa interfaz debería heredar IDisposable incluso (¡quizás especialmente!) Si solo una pequeña fracción de implementaciones necesitarán limpieza. Considere el caso de IEnumerable frente a IEnumerable<T>. Cualquier código que llame al IEnumerable<T>.GetEnumerator() recibirá lo que probablemente sea la única referencia en cualquier parte del universo para un objeto que implemente IDisposable. Como tal, ese código asume la responsabilidad de garantizar que se llame a Dispose en esa referencia. Cualquier código que llame al IEnumerable<T>.GetEnumerator() y no llama al Dispose en el valor devuelto ni lo da a otro código que promete hacerlo, está roto.

El tipo devuelto por IEnumerable.GetEnumerator no genérico no implementa IDisposable. Eso sería por el hecho de que parece sugerir que el código que llama al IEnumerable.GetEnumerator no tiene la responsabilidad de desecharlo. Desafortunadamente, tal implicación es incorrecta. El código que llama al IEnumerable.GetEnumerator tiene la responsabilidad de garantizar que si la instancia particular devuelta implementa IDisposable, debe llamarse al método Dispose. El código que no mantiene esa responsabilidad no está menos roto que el código que no puede disponer la devolución del IEnumerable<T>.GetEnumerator. La falla del tipo de devolución IEnumerable.GetEnumerator (es decir, IEnumerator) para implementar IDisposable no elimina la responsabilidad del que llama al limpiar el objeto devuelto. Simplemente hace que esa responsabilidad sea más onerosa de llevar a cabo, y aumenta la probabilidad de que el código no lo haga.

Cuestiones relacionadas