¿Qué sucede si regreso antes del final de la declaración de uso? ¿Se llamará a la disposición?

115

Tengo el siguiente código

using(MemoryStream ms = new MemoryStream())
{
     //code
     return 0;
}

El dispose()método se llama al final de las usingllaves de declaración, }¿verdad? Dado que returnantes del final de la usingdeclaración, ¿el MemoryStreamobjeto se eliminará correctamente? ¿Qué pasa aquí?

NLV
fuente
4
@JonH: Encuentre el duplicado exacto, luego vote para cerrar en ese caso, por favor.
Noldorin
@Noldorin: Fui a buscar un engaño en esto, porque pensé que debían haberlo preguntado antes, pero no pude encontrar uno. Supongo que todavía hay preguntas fáciles. :)
Randolpho
@JonH y @Noldorin: se habrían presentado duplicados cuando se formó la pregunta, busca "preguntas similares", una característica que la gente parece no usar lo suficiente.
Adam Houldsworth
@ Adam: ve a probarlo tú mismo. Copie / pegue el título y vea qué duplicados presenta el sistema. Te daré una pista: la respuesta es ninguna. Lo mismo ocurre si busca a través de Google o la búsqueda de SO. Parece que esta pregunta no se ha hecho antes.
Randolpho
Aaap ... retiro eso. Acabo de encontrar un duplicado cercano, después de una búsqueda muy dedicada: stackoverflow.com/questions/2641692/… Ahora, la pregunta se hace de manera completamente diferente, pero la pregunta final es prácticamente la misma. Supongo que, después de todo, podemos considerar esto como un engaño.
Randolpho

Respuestas:

167

Sí, Disposese llamará. Se llama tan pronto como la ejecución abandona el alcance del usingbloque, independientemente de los medios que tomó para abandonar el bloque, ya sea el final de la ejecución del bloque, una returndeclaración o una excepción.

Como @Noldorin señala correctamente, el uso de un usingbloque en el código se compila en try/ finally, con la Disposellamada en el finallybloque. Por ejemplo, el siguiente código:

using(MemoryStream ms = new MemoryStream())
{
     //code
     return 0;
}

efectivamente se convierte en:

MemoryStream ms = new MemoryStream();
try
{
    // code
    return 0;
}
finally
{
    ms.Dispose();
}

Por lo tanto, porque finallyse garantiza que se ejecutará después de que el trybloque haya finalizado la ejecución, independientemente de su ruta de ejecución, Disposese garantiza que se llamará, pase lo que pase.

Para obtener más información, consulte este artículo de MSDN .

Apéndice:
Solo una pequeña advertencia para agregar: debido a que Disposese garantiza que se llamará, casi siempre es una buena idea asegurarse de que Disposenunca se produzca una excepción cuando se implementa IDisposable. Por desgracia, hay algunas clases en la biblioteca central que hacen un tiro en ciertas circunstancias cuando Disposese llama - que estoy mirando a ti, proxy de servicio WCF Referencia / cliente! - y cuando eso sucede, puede ser muy difícil rastrear la excepción original si Disposese llamó durante un desenrollado de la pila de excepciones, ya que la excepción original se traga a favor de la nueva excepción generada por la Disposellamada. Puede resultar tremendamente frustrante. ¿O es eso frustrantemente enloquecedor? Uno de los dos. Tal vez ambos.

Randolpho
fuente
4
Creo que encontrará que está compilado de manera efectiva en un bloque de intentar finalmente con una llamada a Disposefinalmente, por lo que está funcionando efectivamente con la implementación de finally, como usted describe.
Noldorin
@Noldorin: exactamente. Aunque supongo que podría ser explícito al respecto. Edición próximamente ....
Randolpho
1
También tenga en cuenta que hay algunas circunstancias en las que no se garantiza que el bloque finalmente se ejecute, como el uso de Environment.FailFast y si se produce una StackOverFlowException.
Christopher McAtackney
@ C.McAtackney: también un buen punto. Además, IIRC, OutOfMemoryException; Básicamente, si no puede detectar la excepción porque es una falla de ejecución crítica, no se llamará a Dispose. Por supuesto, en tal caso, se garantiza que el programa se bloqueará, junto con cualquier memoria asignada, por lo que en el 99.9% de los casos no es un problema, a menos que esté haciendo cosas extrañas como escribir en un archivo en su método de eliminación. . Aparte del catastrófico colapso del programa, claro está.
Randolpho
Nunca debe usar la declaración 'using ()' con WCF; consulte este artículo para obtener más información. Aquí hay un fragmento que uso para los proxies WCF: 'WCFProxy variableName = null; intente {variableName = new WCFProxy (); // TODO codifica aquí variableName.Proxy.Close (); variableName.Dispose (); } catch (Exception) {if (variableName! = null && variableName.Proxy! = null) {variableName.Proxy.Abort (); } lanzar; } '
Dave Black
18

usingLas declaraciones se comportan exactamente como try ... finallybloques, por lo que siempre se ejecutarán en cualquier ruta de salida de código. Sin embargo, creo que están sujetos a muy pocas y raras situaciones en las que finallyno se llaman bloques. Un ejemplo que puedo recordar es si el subproceso en primer plano sale mientras los subprocesos en segundo plano están activos: todos los subprocesos, excepto el GC, están en pausa, lo que significa que los finallybloques no se ejecutan.

Edición obvia: se comportan igual aparte de la lógica que les permite manejar objetos IDisposable, d'oh.

Contenido adicional: se pueden apilar (donde los tipos difieren):

using (SqlConnection conn = new SqlConnection("string"))
using (SqlCommand comm = new SqlCommand("", conn))
{

}

Y también delimitado por comas (donde los tipos son los mismos):

using (SqlCommand comm = new SqlCommand("", conn), 
       SqlCommand comm2 = new SqlCommand("", conn))
{

}
Adam Houldsworth
fuente
4

Su objeto MemoryStream se eliminará correctamente, no hay necesidad de preocuparse por eso.

Otávio Décio
fuente
0

Eche un vistazo a su código en reflector después de compilarlo. Verá que el compilador refactoriza el código para asegurarse de que se llame a dispose en la secuencia.

Wil P
fuente