¿Debo llamar a Close () o Dispose () para objetos de flujo?

151

Clases, tales como Stream, StreamReader, StreamWriteretc. implementos IDisposableinterfaz. Eso significa que podemos llamar al Dispose()método en objetos de estas clases. También han definido un publicmétodo llamado Close(). Ahora eso me confunde, ¿qué debo llamar una vez que haya terminado con los objetos? ¿Qué pasa si llamo a ambos?

Mi código actual es este:

using (Stream responseStream = response.GetResponseStream())
{
   using (StreamReader reader = new StreamReader(responseStream))
   {
      using (StreamWriter writer = new StreamWriter(filename))
      {
         int chunkSize = 1024;
         while (!reader.EndOfStream)
         {
            char[] buffer = new char[chunkSize];
            int count = reader.Read(buffer, 0, chunkSize);
            if (count != 0)
            {
               writer.Write(buffer, 0, count);
            }
         }
         writer.Close();
      }
      reader.Close();
   }
}

Como puede ver, he escrito using()construcciones, que llaman automáticamente al Dispose()método en cada objeto. Pero también llamo Close()métodos. ¿Es correcto?

Sugiérame las mejores prácticas al usar objetos de flujo. :-)

El ejemplo de MSDN no utiliza using()construcciones y Close()método de llamada :

¿Esta bien?

Nawaz
fuente
Si está utilizando ReSharper, puede definir esto como un "antipatrón" dentro del catálogo de patrones. ReSharper marcará cada uso como error / sugerencia / advertencia con respecto a su definición. También es posible definir cómo ReSharper tiene que aplicar un QuickFix para tal ocurrencia.
Thorsten Hans
3
Solo un consejo: puede usar la instrucción using de esa manera para múltiples itens desechables: using (Stream responseStream = response.GetResponseStream ()) using (StreamReader reader = new StreamReader (responseStream)) using (StreamWriter writer = new StreamWriter (filename)) {//..Algunos códigos}
Latrova
No necesita anidar sus declaraciones de uso de esa manera, puede apilarlas una encima de la otra y tener un conjunto de corchetes. En otra publicación, sugerí una edición para un fragmento de código que debería haber usado declaraciones con esa técnica si desea mirar y corregir su "flecha de código": stackoverflow.com/questions/5282999/…
Timothy Gonzalez
2
@ Suncat2000 Puede tener múltiples instrucciones de uso, pero no anidarlas y en su lugar apilarlas. No me refiero a una sintaxis como esta que restringe el tipo: using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) { }. Me refiero a esto donde puedes redefinir el tipo:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) { }
Timothy Gonzalez

Respuestas:

101

Un salto rápido a Reflector.NET muestra que el Close()método StreamWriteres:

public override void Close()
{
    this.Dispose(true);
    GC.SuppressFinalize(this);
}

Y StreamReaderes:

public override void Close()
{
    this.Dispose(true);
}

La Dispose(bool disposing)anulación en StreamReaderes:

protected override void Dispose(bool disposing)
{
    try
    {
        if ((this.Closable && disposing) && (this.stream != null))
        {
            this.stream.Close();
        }
    }
    finally
    {
        if (this.Closable && (this.stream != null))
        {
            this.stream = null;
            /* deleted for brevity */
            base.Dispose(disposing);
        }
    }
}

El StreamWritermétodo es similar.

Entonces, al leer el código, está claro que puede llamar Close()y Dispose()transmitir en línea con la frecuencia que desee y en cualquier orden. No cambiará el comportamiento de ninguna manera.

Por lo tanto, se trata de si es o no más legible de usar Dispose(), Close()y / o using ( ... ) { ... }.

Mi preferencia personal es que using ( ... ) { ... }siempre debe usarse cuando sea posible, ya que le ayuda a "no correr con unas tijeras".

Pero, si bien esto ayuda a la corrección, reduce la legibilidad. En C # ya tenemos una gran cantidad de llaves cerradas, ¿cómo sabemos cuál realmente realiza el cierre en la secuencia?

Así que creo que es mejor hacer esto:

using (var stream = ...)
{
    /* code */

    stream.Close();
}

No afecta el comportamiento del código, pero ayuda a la legibilidad.

Enigmatividad
fuente
20
" En C # ya tenemos una gran cantidad de llaves cerradas, así que, ¿cómo sabemos cuál realiza realmente el cierre en la secuencia? " No creo que este sea un gran problema: la secuencia está cerrada "en el momento adecuado", es decir, cuando la variable sale del alcance y ya no es necesaria.
Heinzi
110
Hmm, no, eso es un "¿por qué diablos lo está cerrando dos veces?" golpe de velocidad mientras lee.
Hans Passant
57
No estoy de acuerdo con la Close()llamada redundante . Si alguien con menos experiencia mira el código y no lo sabe, usingél: 1) lo buscará y aprenderá , o 2) agregará ciegamente un Close()manual. Si elige 2), tal vez algún otro desarrollador verá el redundante Close()y, en lugar de "reírse", instruya al desarrollador menos experimentado. No estoy a favor de hacerles la vida difícil a los desarrolladores sin experiencia, pero estoy a favor de convertirlos en desarrolladores experimentados.
R. Martinho Fernandes
14
Si usa el uso de + Cerrar () y activa / analiza, obtiene "advertencia: CA2202: Microsoft. Uso: El objeto 'f' puede desecharse más de una vez en el método 'Foo (cadena)'. Para evitar generar un Sistema. ObjectDisposedException no debe llamar a Dispose más de una vez en un objeto .: Líneas: 41 "Entonces, aunque la implementación actual está bien con llamar a Close and Dispose, de acuerdo con la documentación y / o análisis, no está bien y podría cambiar en futuras versiones de. red.
marc40000
44
+1 por la buena respuesta. Otra cosa a tener en cuenta. ¿Por qué no agregar un comentario después de la llave de cierre como // Cerrar o, como hago, siendo un novato, agrego un forro después de cualquier llave de cierre que no está claro? por ejemplo, en una clase larga, agregaría // Fin del espacio de nombres XXX después de la llave de cierre final, y // Fin de la clase AAA después de la segunda llave de cierre final. ¿No es esto para lo que son los comentarios? Sólo curioso. :) Como novato, vi ese código, hense por qué vine aquí. Hice la pregunta "¿Por qué la necesidad del segundo cierre". Siento que líneas adicionales de código no agregan claridad. Lo siento.
Francis Rodgers
51

No, no debe llamar a esos métodos manualmente. Al final del usingbloque Dispose(), se llama automáticamente al método que se encargará de liberar recursos no administrados (al menos para las clases estándar de .NET BCL, como secuencias, lectores / escritores, ...). Entonces también podría escribir su código de esta manera:

using (Stream responseStream = response.GetResponseStream())
    using (StreamReader reader = new StreamReader(responseStream))
        using (StreamWriter writer = new StreamWriter(filename))
        {
            int chunkSize = 1024;
            while (!reader.EndOfStream)
            {
                 char[] buffer = new char[chunkSize];
                 int count = reader.Read(buffer, 0, chunkSize);
                 if (count != 0)
                 {
                     writer.Write(buffer, 0, count);
                 }
            }
         }

El Close()método llama Dispose().

Darin Dimitrov
fuente
1
Estoy bastante seguro de que no necesita ser usingel primero, responseStreamya que está envuelto por el readerque se asegurará de que esté cerrado cuando el lector esté dispuesto. +1 no obstante
Isak Savo el
Esto es confuso cuando dijiste The Close method calls Dispose.... y en el resto de tu publicación, estás insinuando que Dispose()llamaría Close(), no debería llamar a esta última manualmente. ¿Estás diciendo que se llaman?
Nawaz
@Nawaz, mi publicación fue confusa. El método Close simplemente llama a Dispose. En su caso, necesita Eliminar para liberar recursos no administrados. Al envolver su código en una declaración de uso, se llama al método Dispose.
Darin Dimitrov
3
Pésima respuesta. Se supone que puede usar un usingbloque. Estoy implementando una clase que escribe de vez en cuando y, por lo tanto, no puedo.
Jez
55
@Jez Su clase debe implementar la interfaz IDisposable, y posiblemente también Close () si close es una terminología estándar en el área , de modo que las clases que usan su clase puedan usar using(o, nuevamente, ir al Dispose Pattern).
Dorus
13

La documentación dice que estos dos métodos son equivalentes:

StreamReader.Close : esta implementación de Close llama al método Dispose pasando un valor verdadero.

StreamWriter.Close : esta implementación de Close llama al método Dispose pasando un valor verdadero.

Stream.Close : este método llama a Dispose, especificando true para liberar todos los recursos.

Entonces, ambos son igualmente válidos:

/* Option 1, implicitly calling Dispose */
using (StreamWriter writer = new StreamWriter(filename)) { 
   // do something
} 

/* Option 2, explicitly calling Close */
StreamWriter writer = new StreamWriter(filename)
try {
    // do something
}
finally {
    writer.Close();
}

Personalmente, me quedaría con la primera opción, ya que contiene menos "ruido".

Heinzi
fuente
5

En muchas clases que admiten ambos Close()y Dispose()métodos, las dos llamadas serían equivalentes. En algunas clases, sin embargo, es posible volver a abrir un objeto que se ha cerrado. Algunas de esas clases pueden mantener vivos algunos recursos después de un Cierre, para permitir la reapertura; otros pueden no mantener ningún recurso vivo Close(), pero pueden establecer una bandera Dispose()para prohibir explícitamente la reapertura.

El contrato IDisposable.Disposerequiere explícitamente que llamarlo a un objeto que nunca se volverá a usar será inofensivo, por lo que recomendaría llamar a uno IDisposable.Disposeo a un método llamado Dispose()a cada IDisposableobjeto, ya sea que uno también llame o no Close().

Super gato
fuente
FYI aquí hay un artículo en los blogs de MSDN que explica la diversión Cerrar y desechar. blogs.msdn.com/b/kimhamil/archive/2008/03/15/…
JamieVer
1

Esta es una pregunta antigua, pero ahora puede escribir usando declaraciones sin necesidad de bloquear cada una. Se eliminarán en orden inverso cuando finalice el bloque contenedor.

using var responseStream = response.GetResponseStream();
using var reader = new StreamReader(responseStream);
using var writer = new StreamWriter(filename);

int chunkSize = 1024;
while (!reader.EndOfStream)
{
    char[] buffer = new char[chunkSize];
    int count = reader.Read(buffer, 0, chunkSize);
    if (count != 0)
    {
        writer.Write(buffer, 0, count);
    }
}

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using

Todd Skelton
fuente