Uso de instrucción vs. IDisposable.Dispose ()

Tengo entendido que la instrucción using en .NET llama a un IDisposable Dispose() del objeto IDisposable una vez que el código sale del bloque.

¿La instrucción using hace algo más? De lo contrario, parecería que los siguientes dos ejemplos de código logran exactamente lo mismo:

 Using Con as New Connection() Con.Open() 'do whatever ' End Using Dim Con as New Connection() Con.Open() 'do whatever ' Con.Dispose() 

Daré la mejor respuesta a quien confirme que estoy en lo cierto o que estoy equivocado y explico por qué. Tenga en cuenta que soy consciente de que ciertas clases pueden hacer cosas diferentes en sus métodos Dispose() . Esta pregunta se trata de si la instrucción de using logra el mismo resultado que llamar al método Dispose() un objeto.

using es básicamente el equivalente de:

 try { // code } finally { obj.Dispose(); } 

Por lo tanto, también tiene la ventaja de invocar a Dispose() incluso si se lanza una excepción no controlada al código dentro del bloque.

Como dijo Brian Warshaw aquí , es simplemente una implementación de try y finally bloquear para asegurarse de que el objeto se elimine. Agregando a su respuesta, el using bloque también asegura que el objeto se elimine incluso si regresa dentro utilizando el scope .

Una vez tuve curiosidad sobre esto y lo probé usando el siguiente enfoque:

Clase de prueba IDisposable personalizada y Principal

 private class DisposableTest : IDisposable { public string Name { get; set; } public void Dispose() { Console.WriteLine("{0}.Dispose() is called !", Name); } } public static void Main(string[] args) { try { UsingReturnTest(); UsingExceptionTest(); } catch { } try { DisposeReturnTest(); DisposeExceptionTest(); } catch { } DisposeExtraTest(); Console.ReadLine(); } 

Implementación de casos de prueba

 private static string UsingReturnTest() { using (DisposableTest usingReturn = new DisposableTest() { Name = "UsingReturn" }) { return usingReturn.Name; } } private static void UsingExceptionTest() { using (DisposableTest usingException = new DisposableTest() { Name = "UsingException" }) { int x = int.Parse("NaN"); } } private static string DisposeReturnTest() { DisposableTest disposeReturn = new DisposableTest() { Name = "DisposeReturn" }; return disposeReturn.Name; disposeReturn.Dispose(); // # IDE Warning; Unreachable code detected } private static void DisposeExceptionTest() { DisposableTest disposeException = new DisposableTest() { Name = "DisposeException" }; int x = int.Parse("NaN"); disposeException.Dispose(); } private static void DisposeExtraTest() { DisposableTest disposeExtra = null; try { disposeExtra = new DisposableTest() { Name = "DisposeExtra" }; return; } catch { } finally { if (disposeExtra != null) { disposeExtra.Dispose(); } } } 

Y la salida es:

  • Usando Return.Dispose () es llamado!
  • UsingException.Dispose () se llama!
  • ¡Se llama a DisposeExtra.Dispose ()!
 //preceeding code using (con = new Connection()) { con.Open() //do whatever } //following code 

es equivalente a lo siguiente (tenga en cuenta el scope limitado de estafa):

 //preceeding code { var con = new Connection(); try { con.Open() //do whatever } finally { if (con != null) con.Dispose(); } } //following code 

Esto se describe aquí: http://msdn.microsoft.com/en-us/library/yh598w02.aspx

La instrucción using garantiza que se invoque Dispose incluso si se produce una excepción mientras se están llamando métodos al objeto. Puede lograr el mismo resultado colocando el objeto dentro de un bloque try y luego llamando a Dispose en un bloque finally; de hecho, así es como el comstackdor traduce la sentencia using .

Una instrucción de using es más clara y concisa que un try...finally{Dispose()} constructo, y debe usarse en casi todos los casos donde uno no quiere permitir que un bloque salga sin que se Dispose . Las únicas situaciones comunes donde la eliminación “manual” sería mejor sería cuando:

  1. Un método llama a un método de fábrica que devuelve algo que puede o no implementar `IDisposable`, pero que debería ser` Dispose`d si lo hace (un escenario que ocurre con `IEnumerable.GetEnumerator ()` no genérico). Interfaces de fábrica bien diseñadas deberían devolver un tipo que implemente `IDisposable` (quizás con una implementación do-nothing, como suele ser el caso de` IEnumerator`) o, de lo contrario, especifique que los llamadores no deben `Dispose` el objeto devuelto. Desafortunadamente, algunas interfaces como `IEnumerable` no genérico no satisfacen ningún criterio. Tenga en cuenta que uno no puede usar `using` en tales casos, ya que solo funciona con ubicaciones de almacenamiento cuyo tipo declarado implementa` IDisposable`.
  2. Se espera que el objeto `IDisposable` viva incluso después de salir del bloque (como suele ser el caso cuando se establece un campo` IDisposable`, o se devuelve un `IDisposable` de un método de fábrica).

Tenga en cuenta que al devolver un IDisposable de un método de fábrica, uno debe usar algo como lo siguiente:

   bool ok = falso;
   DesechableClass myThing;
   tratar
   {
     myThing = new DisposableClass ();
     ...
     ok = cierto;
     devolver myThing;
   }
   finalmente
   {
     si (! ok)
     {
       if (myThing! = null)
         myThing.Dispose ();
     }
   }

para garantizar que myThing obtendrá Dispose d si no se devuelve. Desearía que hubiera una forma de emplear using junto con algún método de “cancelar eliminación”, pero eso no existe.

La diferencia entre los dos es que, si se lanza una excepción

 Con.Open() 'do whatever 

Con.Dispose no se llamará.

No estoy al tanto de la syntax de VB, pero en C #, el código equivalente sería

 try { con = new Connection(); // Do whatever } finally { if (con != null) con.Dispose(); } 

La instrucción using garantiza que el objeto se elimina en caso de que se genere una excepción. Es el equivalente a llamar a disponer en un bloque final.

El uso envuelve el bloque incluido en un try / finally que llama a Dispose en el bloque finally. Esto asegura que se llamará a Dispose incluso si se produce una excepción.

Debe usarlo en casi todos los casos, por razones de seguridad

El bloque que usa se asegura de que se Dispose() si se lanza una excepción.

Tu segunda muestra no hace eso.

Si Con.Open() arrojó una excepción, en el primer caso se le garantiza que se llama a Con.Dispose() . En el segundo caso, la excepción se propaga y Con.Dispose() no se llamará.

Si la memoria le sirve, usar es una garantía de que un objeto se desecha independientemente de cómo el bloque de código que lo rodea lo abandona. Lo hace rodeando el bloque en un bash … finalmente bloqueando, y comprobando la nulidad de la variable utilizada , y luego deshaciéndose de ella si no es nula. Si se lanzó una excepción, se permite que suba la stack. Aparte de eso, todo lo que hace es garantizar la eliminación de objetos desechables no nulos.

 try { var myDisposable = new DisposableObject(); myDisposable.DoSomething(); } finally { if (myDisposable != null) ((IDisposable)myDisposable).Dispose(); }