2010-04-20 1010 views
30

Necesito forzar el uso de "usar" para disponer de una nueva instancia de una clase.¿Es posible forzar el uso de "usar" para clases desechables?

public class MyClass : IDisposable 
{ 
    ... 
} 

using(MyClass obj = new MyClass()) // Force to use "using" 
{ 
} 
+0

¿Por qué * debe * un usuario de su clase desecharlo? Normalmente, recomendamos encarecidamente que lo desechen para liberar recursos antes. ¿Qué tiene de especial tu caso? –

+2

Si pudiera hacer eso, el recurso solo se podría usar en el alcance del método. Sin duda, haría las cosas más fáciles, pero también limitaría el uso de manera significativa. –

+0

@Brian Rasmussen: A veces las limitaciones pueden liberarlo para que se preocupe por cosas más importantes. – LBushkin

Respuesta

1

No, no puedes hacer eso. Ni siquiera puedes obligarlos a llamar a disponer. Lo mejor que puedes hacer es agregar un finalizador. Solo tenga en cuenta que se llamará al finalizador cuando se elimine el objeto y eso depende del tiempo de ejecución.

1

No, no es posible. Ahora lo que puede hacer es llamar al método de eliminación en el finalizador de la clase (y luego puede suprimir el uso si llama al método de eliminación). De esa forma se disparará si no se hace explícitamente en el código.

Este enlace le mostrará cómo implementar el finalizador/eliminarlo patrón:

http://www.devx.com/dotnet/Article/33167

0

Si quiere forzar a utilizar el uso de esta clase, el código para apoyar esta clase se pueden codificar en otra clase y ocultar MyClass para uso normal.

+0

Esto no funcionaría porque su clase "contenedora" tendría que implementar IDisposable para permitirle deshacerse de la clase original y usted de vuelta al cuadrado uno, usando un finalizador. – juharr

+0

No, la clase contenedora puede no implementar IDisposable no es metter, él controla la eliminación de MyClass. Sí, la envoltura es deshacerse de su elemento hijo (por ejemplo, MyClass), pero él puede deshacerse de MyClass una vez que termine, y la clase Wrapper puede vivir hace mucho tiempo esta operación. – Svisstack

4

Me pregunto si FXCop podría hacer cumplir esa regla?

+2

Sí, existe la regla DisposeObjectsBeforeLosingScope. Ver: http://blogs.msdn.com/codeanalysis/archive/2010/03/22/what-s-new-in-code-analysis-for-visual-studio-2010.aspx – LBushkin

5

La instrucción using es una abreviatura que el compilador convierte a partir de:

(using DisposableObject d = new DisposableObject()){} 

en:

DisposableObject d = new DisposableObject() 
try 
{ 

} 
finally 
{ 
    if(d != null) d.Dispose(); 
} 

por lo que están más o menos preguntando si es posible cumplir escribir un try/finally bloque que llama a Dispose para un objeto.

+3

* Técnicamente *, lo haría be '((IDisposable) d) .Dispose();';) –

+0

En realidad * es * una diferencia entre ellos. Consulte http://blogs.msdn.com/mhop/archive/2006/12/12/implicit-and-explicit-interface-implementations.aspx para consultar la implementación de la interfaz implícita frente a la explícita, que incluye ejemplos en los que la conversión a la interfaz importa. . – Brian

+0

@Brian, gracias por el enlace. por lo tanto, es (IDisposable) d porque si Dispose() se implementa explícitamente, puede estar oculto desde d a menos que se lance a la interfaz. – Grokodile

44

El hecho de que necesite para asegurarse de que el objeto está dispuesto indica una falla de diseño. Está bien si la eliminación es el educado o eficiente que hacer, pero no debe ser semánticamente necesario.

No hay forma de exigir que un objeto se elimine a través de la declaración using. Sin embargo, lo que puede hacer es mantener un indicador en el objeto que indique si el objeto fue eliminado o no, y luego escribir un finalizador que verifique ese indicador. Si el finalizador detecta que el objeto no fue eliminado, entonces puede hacer que el finalizador, por ejemplo, termine el proceso mediante failfast. Es decir, castigue severamente al usuario que se olvidó de eliminar el objeto que está obligado a reparar su error o dejar de usar su objeto.

Eso no me parece agradable, bueno o educado, pero usted es el único que sabe cuáles son las terribles y terribles consecuencias de no disponer del objeto. Si aplicar un castigo a las personas que no siguen sus reglas locas es mejor que vivir con las consecuencias de no seguir las reglas es decisión suya.

+4

@Eric: Es una coincidencia graciosa, pero he publicado una pregunta similar a su blog sobre este tema el día de hoy. Un comentario que haré es que la eliminación se vuelve semánticamente necesaria cuando se trata de recursos muy limitados cuyo consumo puede afectar adversamente un proceso completo. Sería útil si el compilador pudiera hacer cumplir (o al menos advertir) cuando determinados recursos desechables no se liberen de manera determinista; para este propósito sugerí un atributo (Regla semántica necesaria, o similar) que podría etiquetarse en los tipos que implementan IDisposable. – LBushkin

+1

Me parece curioso este comentario, ya que en la mayoría de los casos los destructores/finalizadores solo se ejecutarán si el código tiene errores, y en otros parece que es mejor que falle el código erróneo que enmascarar los errores y cojear. Excepto en el caso de objetos inmutables compartidos (por ejemplo, un 'Bitmap') que son razonablemente seguros para el abandono si se almacenan en caché mediante' WeakReference', no veo muchas excusas para no "Dispose' todo (aunque encuentro la dificultad de llamar 'Dispose' en un objeto cuyo constructor arroja una excepción para ser muy fastidioso). – supercat

+0

Leer esta respuesta nuevamente después de años me hace pensar que forzar la eliminación de un recurso es una mala elección de diseño cuando el recurso es algún tipo de almacenamiento. A pesar de esto, el uso de declaración se usa a menudo cuando se trata de bloqueos, ofreciendo una sintaxis limpia para operar en recursos dentro del contexto de un bloqueo. Forzar la adquisición/liberación de un bloqueo en un bloque de ámbito es otra vez una opción de diseño, pero no creo que pueda considerarse malo en general. En estos casos, no hay forma de aplicar la declaración 'using' y uno debe confiar en los recursos de diseño de la API. – ceztko

21

Es feo, pero se puede hacer algo como esto:

public sealed class DisposableClass : IDisposable 
    { 
     private DisposableClass() 
     { 

     } 

     public void Dispose() 
     { 
      //Dispose... 
     } 

     public static void DoSomething(Action<DisposableClass> doSomething) 
     { 
      using (var disposable = new DisposableClass()) 
      { 
       doSomething(disposable); 
      } 
     } 
    } 
+0

Este es un uso muy interesante del estilo de paso de continuación (CPS), pero no sé si realmente podría ser extensible al caso general de adquisición y liberación de recursos. – LBushkin

+0

Esto no es feo, cambie el nombre 'DoSomething()' a 'DoWithDisposableClass()' y muévalo a fábrica. – LuckyLikey

+0

¿y si múltiples métodos desconocidos pueden significar 'doSomething'? – denfromufa

0

Usted debe mirar en RAII, que es una técnica que asegura que los recursos adquiridos serán desechados adecuadamente.

Lo que quiero decir es que si no puede forzar que se llame al método Dispose (a través del using o directamente), puede poner su contenido dentro de otro método que se llamará, como el destructor.

Es un patrón común para implementar IDisposable de la siguiente manera:

// Implement IDisposable. 
// Do not make this method virtual. 
// A derived class should not be able to override this method. 
public void Dispose() 
{ 
    Dispose(true); 
    // This object will be cleaned up by the Dispose method. 
    // Therefore, you should call GC.SupressFinalize to 
    // take this object off the finalization queue 
    // and prevent finalization code for this object 
    // from executing a second time. 
    GC.SuppressFinalize(this); 
} 

// Dispose(bool disposing) executes in two distinct scenarios. 
// If disposing equals true, the method has been called directly 
// or indirectly by a user's code. Managed and unmanaged resources 
// can be disposed. 
// If disposing equals false, the method has been called by the 
// runtime from inside the finalizer and you should not reference 
// other objects. Only unmanaged resources can be disposed. 
private void Dispose(bool disposing) 
{ 
    // Check to see if Dispose has already been called. 
    if(!this.disposed) 
    { 
     // If disposing equals true, dispose all managed 
     // and unmanaged resources. 
     if(disposing) 
     { 
      // Dispose managed resources. 
      component.Dispose(); 
     } 

     // Call the appropriate methods to clean up 
     // unmanaged resources here. 
     // If disposing is false, 
     // only the following code is executed. 

     // TODO: write code 
    } 
    disposed = true;   
} 

// Use C# destructor syntax for finalization code. 
// This destructor will run only if the Dispose method 
// does not get called. 
// It gives your base class the opportunity to finalize. 
// Do not provide destructors in types derived from this class. 
~ClassName() 
{ 
    // Do not re-create Dispose clean-up code here. 
    // Calling Dispose(false) is optimal in terms of 
    // readability and maintainability. 
    Dispose(false); 
} 

Fuente: http://msdn.microsoft.com/en-us/library/system.gc.suppressfinalize.aspx

0

Si desea forzar la eliminación de los recursos withing un ámbito que es posible, pero IDisposable no es realmente necesario . Con el siguiente código:

public class ResourceHandle 
{ 
    public delegate void ResourceProvider(Resource resource); 

    private string _parms; 

    public ResourceHandle(string parms) 
    { 
     _parms = parms; 
    } 

    public void UseResource(ResourceProvider useResource) 
    { 
     Resource resource = new Resource(_parms); 
     useResource(resource); 
     resource.Close(); 
    } 
} 


public class Resource 
{ 
    private string _parms; 

    internal Resource(string parms) 
    { 
     // Initialize resource 
    } 

    internal void Close() 
    { 
     // Do cleaning 
    } 

    // Public methods of resource 
} 

Usted puede utilizar el recurso sólo de esta manera:

public void foo() 
{ 
    ResourceHandle resourceHandle = new ResourceHandle("parms"); 

    resourceHandle.UseResource(delegate(Resource resource) 
     { 
      // use resource 
     }); 
} 

Como se puede ver IDisposable no es realmente necesario aquí.

11

Puede escribir su propia advertencia/error con el uso del marco de Roslyn. Su DiagnosticAnalyzer verificaría todas las llamadas de constructor para ver si su clase se está construyendo o no y si se encuentra dentro de una declaración using o no.

El diagnóstico notificado se puede establecer en Gravedad de error y se puede marcar como no configurable, lo que significa que nadie puede degradarlo a advertencia o información.

Además, si está desarrollando una biblioteca de Nuget es posible que desee enviar su analizador como una dependencia de desarrollo y agregarlo como un paquete analizador nuget. Esto daría lugar a que todos sus usuarios se vean obligados a deshacerse de su clase determinada. Este embalaje se conoce como "code-aware library".

Tenga en cuenta que, en teoría, esto podría hacerse por una tercera biblioteca analizador de fiesta también (como FxCop), pero hay muchas implementaciones IDisposable que no necesitan ser estrictamente dispuesto, como MemoryStream, cuya Dispose no hace mucho, entonces estas reglas tienen algunos mecanismos de inclusión de listas blancas o reportan falsos positivos.

Cuestiones relacionadas