visual variable uso tipos sirve que para net implementar end c# dispose using

variable - tipos de using en c#



¿Hay una situación en la cual Dispose no se llamará para un bloque ''using''? (8)

Esta fue una pregunta de la entrevista telefónica que tuve: ¿Hay un momento en que no se invocará Dispose en un objeto cuyo ámbito está declarado por un bloque de uso?

Mi respuesta fue no, incluso si ocurre una excepción durante el uso del bloque, Dispose se seguirá llamando.

El entrevistador no estuvo de acuerdo y dijo que si el using está envuelto en un bloque de try , entonces no se llamará a Dispose cuando ingrese al bloque catch.

Esto va en contra de mi comprensión de la construcción, y no he podido encontrar nada que respalde el punto de vista de los entrevistadores. ¿Está correcto o podría haber entendido mal la pregunta?


Cuatro cosas que harán que Dispose no se invoque en un bloque de uso:

  1. Una falla de energía en su máquina cuando está dentro del bloque de uso.
  2. Su máquina se derrite con una bomba atómica mientras está en el interior del bloque de uso.
  3. Excepciones imposibles de detectar, como Exception , AccessViolationException y posiblemente otras .
  4. Environment.FailFast

Curiosamente, leí sobre una circunstancia en la que Dispose no se llamará en un bloque de uso esta mañana. Consulte este blog en MSDN. Se trata de usar Dispose con IEnumerable y la palabra clave yield, cuando no se itera toda la colección.

Lamentablemente, esto no se trata con el caso de excepción, sinceramente, no estoy seguro de eso. Hubiera esperado que se hiciera, pero tal vez valga la pena verificarlo con un poco de código.


El bloque de using se convierte por el compilador en un bloque try / finally propio dentro del bloque try existente.

Por ejemplo:

try { using (MemoryStream ms = new MemoryStream()) throw new Exception(); } catch (Exception) { throw; }

se convierte

.try { IL_0000: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor() IL_0005: stloc.0 .try { IL_0006: newobj instance void [mscorlib]System.Exception::.ctor() IL_000b: throw } // end .try finally { IL_000c: ldloc.0 IL_000d: brfalse.s IL_0015 IL_000f: ldloc.0 IL_0010: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0015: endfinally } // end handler } // end .try catch [mscorlib]System.Exception { IL_0016: pop IL_0017: rethrow } // end handler

El compilador no reorganizará las cosas. Entonces sucede así:

  1. Se lanza una excepción, o se propaga a, la parte de try del bloque de using
  2. El control sale de la parte de try del bloque que using , y entra en su parte finally
  3. El objeto está dispuesto por el código en el bloque finally
  4. El control deja el bloque finalmente, y la excepción se propaga al try externo
  5. Control deja la try externa y entra en el manejador de excepciones

Siendo el punto, el bloque interno final siempre se ejecuta antes de la catch externa, porque la excepción no se propaga hasta que finaliza el bloque final.

El único caso normal donde esto no sucederá es en un generador (perdón, "iterador"). Un iterador se convierte en una máquina de estado semicomplicada y, finally , no se garantiza que los bloques se ejecuten si no se puede acceder después de un yield return (pero antes de que se haya eliminado).


El entrevistador tiene parcialmente razón. Dispose no puede limpiar correctamente el objeto subyacente caso por caso.

WCF, por ejemplo, tiene algunos problemas conocidos si se lanza una excepción mientras se está usando un bloque. Su entrevistador probablemente estaba pensando en esto.

Aquí hay un artículo de MSDN sobre cómo evitar problemas con el bloque de uso con WCF. Aquí está la solución oficial de Microsoft , aunque ahora creo que una combinación de esa respuesta y esta es el enfoque más elegante.


Las otras respuestas sobre falla de energía, Environment.FailFast() , iteradores o trampas al using algo que es null son todas interesantes. Pero me parece curioso que nadie mencionara lo que creo que es la situación más común cuando Dispose() no se llamará incluso en presencia de using : cuando la expresión inside using arroja una excepción.

Por supuesto, esto es lógico: la expresión al using arrojó una excepción, por lo que la asignación no tuvo lugar y no hay nada que podamos llamar Dispose() en. Pero el objeto desechable ya puede existir, aunque puede estar a la mitad en estado inicializado. E incluso en este estado, ya puede contener algunos recursos no administrados. Esta es otra razón por la cual la implementación correcta del patrón desechable es importante.

Ejemplo del código problemático:

using (var f = new Foo()) { // something } … class Foo : IDisposable { UnmanagedResource m_resource; public Foo() { // obtain m_resource throw new Exception(); } public void Dispose() { // release m_resource } }

Aquí, parece que Foo lanza m_resource correctamente y estamos using correctamente también. Pero el Dispose() en Foo nunca se llama, debido a la excepción. La solución en este caso es usar el finalizador y liberar el recurso allí también.


Sí, hay un caso en el que no se llamará a disponer ... está pensando demasiado. El caso es cuando la variable en el bloque de uso es null

class foo { public static IDisposable factory() { return null; } } using (var disp = foo.factory()) { //do some stuff }

no arrojará una excepción, pero si se dispusiera se llamaría en todos los casos. Sin embargo, el caso específico que su entrevistador mencionó es incorrecto.


using (var d = new SomeDisposable()) { Environment.FailFast("no dispose"); }


void Main() { try { using(var d = new MyDisposable()) { throw new Exception("Hello"); } } catch { "Exception caught.".Dump(); } } class MyDisposable : IDisposable { public void Dispose() { "Disposed".Dump(); } }

Esto produjo:

Disposed Exception caught

Así que estoy de acuerdo contigo y no con el entrevistador inteligente ...