Devo chamar Close () ou Dispose () para objetos de fluxo?

151

Classes, tais como Stream, StreamReader, StreamWriteretc implementos IDisposableinterface. Isso significa que podemos chamar o Dispose()método nos objetos dessas classes. Eles também definiram um publicmétodo chamado Close(). Agora, isso me confunde, sobre o que devo chamar quando terminar os objetos? E se eu ligar para os dois?

Meu código atual é 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 você vê, escrevi using()construções, que automaticamente chamam Dispose()método em cada objeto. Mas eu também chamo Close()métodos. Está certo?

Sugira-me as melhores práticas ao usar objetos de fluxo. :-)

O exemplo do MSDN não usa using()construções e chama o Close()método:

Isso é bom?

Nawaz
fonte
Se você estiver usando o ReSharper, poderá defini-lo como um "antipadrão" no catálogo de patter. O ReSharper marcará cada uso como erro / dica / aviso referente à sua definição. Também é possível definir como o ReSharper deve aplicar um QuickFix para essa ocorrência.
Thorsten Hans
3
Apenas uma dica: Você pode usar a instrução using assim para vários itens descartáveis: using (Stream responseStream = response.GetResponseStream ()) using (StreamReader reader = new StreamReader (responseStream)) using (StreamWriter writer = new StreamWriter (filename)) {//..Alguns códigos}
Latrova 22/09/14
Você não precisa aninhar suas instruções de uso assim, pode empilhá-las umas sobre as outras e ter um conjunto de colchetes. Em outra postagem, sugeri uma edição para um trecho de código que deveria ter usado instruções com essa técnica se você deseja procurar e corrigir sua "seta de código": stackoverflow.com/questions/5282999/…
Timothy Gonzalez
2
@ Suncat2000 É possível ter várias instruções using, mas não aninhar e empilhá-las. Não me refiro a sintaxe como esta que restringe o tipo: using (MemoryStream ms1 = new MemoryStream(), ms2 = new MemoryStream()) { }. Quero dizer assim, onde você pode redefinir o tipo:using (MemoryStream ms = new MemoryStream()) using (FileStream fs = File.OpenRead("c:\\file.txt")) { }
Timothy Gonzalez

Respostas:

101

Um rápido salto no Reflector.NET mostra que o Close()método ativado StreamWriteré:

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

E StreamReaderé:

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

A Dispose(bool disposing)substituição StreamReaderé:

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);
        }
    }
}

O StreamWritermétodo é semelhante.

Portanto, lendo o código, fica claro que você pode ligar Close()e Dispose()transmitir sempre que quiser e em qualquer ordem. Não vai mudar o comportamento de forma alguma.

Então, tudo se resume a saber se é ou não é mais legível para uso Dispose(), Close()e / ou using ( ... ) { ... }.

Minha preferência pessoal é que using ( ... ) { ... }sempre deve ser usado quando possível, pois ajuda você a "não correr com tesoura".

Mas, embora isso ajude à correção, reduz a legibilidade. No C #, já temos uma infinidade de chaves de fechamento, então como sabemos qual delas realmente executa o fechamento no fluxo?

Então eu acho que é melhor fazer isso:

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

    stream.Close();
}

Não afeta o comportamento do código, mas ajuda na legibilidade.

Enigmatividade
fonte
20
" No C #, já temos uma infinidade de chaves de fechamento, então como sabemos qual delas realmente executa o fechamento do fluxo? " Não acho que esse seja um grande problema: o fluxo está fechado "no momento certo", ou seja, quando a variável sai do escopo e não é mais necessária.
Heinzi 23/09
110
Hmm, não, isso é um "por que diabos ele está fechando duas vezes ??" lombada durante a leitura.
Hans Passant 23/09
57
Discordo da Close()chamada redundante . Se alguém menos experiente usingexaminar o código e não souber sobre ele: 1) procurará e aprenderá ou 2) adicionará um cegamente Close()manualmente. Se ele escolher 2), talvez algum outro desenvolvedor veja o redundante Close()e, em vez de "rir", instrua o desenvolvedor menos experiente. Não sou a favor de dificultar a vida de desenvolvedores inexperientes, mas sou a favor de transformá-los em desenvolvedores experientes.
R. Martinho Fernandes
14
Se você usar + Fechar () e ativar / analisar, você recebe "aviso: CA2202: Microsoft.Usage: O objeto 'f' pode ser descartado mais de uma vez no método 'Foo (string)'. Para evitar a geração de um sistema. ObjectDisposedException, você não deve chamar Dispose mais de uma vez em um objeto .: Linhas: 41 "Portanto, embora a implementação atual seja boa com a chamada Close and Dispose, de acordo com a documentação e / análise, não está ok e pode ser alterado nas versões futuras do. internet.
Marc40000
4
+1 para a boa resposta. Outra coisa a considerar. Por que não adicionar um comentário após a chave de fechamento, como // Fechar ou como eu, sendo um novato, adiciono um liner após qualquer chave de fechamento que não esteja clara. como por exemplo em uma classe longa, eu adicionaria // End Namespace XXX após a chave de fechamento final e // End Class YYY após a segunda chave de fechamento final. Não é para isso que servem os comentários. Apenas curioso. :) Como um novato, eu vi esse código, entendo porque eu vim aqui. Fiz a pergunta "Por que a necessidade do segundo fechamento". Sinto que linhas extras de código não aumentam a clareza. Desculpe.
Francis Rodgers
51

Não, você não deve chamar esses métodos manualmente. No final do usingbloco, o Dispose()método é chamado automaticamente, o qual terá o cuidado de liberar recursos não gerenciados (pelo menos para as classes .NET BCL padrão, como fluxos, leitores / gravadores, ...). Então você também pode escrever seu código assim:

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);
                 }
            }
         }

O Close()método chama Dispose().

Darin Dimitrov
fonte
1
Tenho certeza de que você não precisa ser usingo primeiro, responseStreampois é embalado pelo readerque garantirá que seja fechado quando o leitor for descartado. +1 no entanto
Isak Savo 23/09
Isso é confuso quando você disse The Close method calls Dispose.... e no restante do seu post, você está insinuando que Dispose()chamaria Close(), eu não deveria ligar manualmente para o último. Você está dizendo que eles se chamam?
Nawaz
@Nawaz, meu post foi confuso. O método Close simplesmente chama Dispose. No seu caso, você precisa de Dispose para liberar recursos não gerenciados. Ao envolver seu código na instrução using, o método Dispose é chamado.
Darin Dimitrov
3
Resposta terrível. Assume que você pode usar um usingbloco. Estou implementando uma classe que escreve de tempos em tempos e, portanto, não pode.
Jez
5
@Jez Sua classe deve implementar a interface IDisposable e, possivelmente, também Close () se close for uma terminologia padrão na área , para que as classes que usam sua classe possam usar using(ou, novamente, vá para o Dispose Pattern).
Dorus
13

A documentação diz que esses dois métodos são equivalentes:

StreamReader.Close : Esta implementação de Close chama o método Dispose passando um valor verdadeiro.

StreamWriter.Close : Esta implementação de Close chama o método Dispose passando um valor verdadeiro.

Stream.Close : este método chama Dispose, especificando true para liberar todos os recursos.

Portanto, ambos são 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();
}

Pessoalmente, eu continuaria com a primeira opção, pois ela contém menos "ruído".

Heinzi
fonte
5

Em muitas classes que suportam ambos Close()e Dispose()métodos, as duas chamadas seriam equivalentes. Em algumas classes, no entanto, é possível reabrir um objeto que foi fechado. Algumas dessas classes podem manter alguns recursos ativos após um fechamento, a fim de permitir a reabertura; outros podem não manter nenhum recurso ativo Close(), mas podem definir um sinalizador Dispose()para proibir explicitamente a reabertura.

O contrato para IDisposable.Disposeexplicitamente exige que chamá-lo em um objeto que nunca será usado novamente seja, na pior das hipóteses, inofensivo, portanto, recomendo chamar IDisposable.Disposeum método ou um método chamado Dispose()em cada IDisposableobjeto, independentemente de alguém também chamar Close().

supercat
fonte
Para sua informação, aqui está um artigo nos blogs do MSDN que explica a diversão Fechar e Dispor. Como você pode ver, é possível que você não tenha uma conta de
mail cadastrada
1

Esta é uma pergunta antiga, mas agora você pode escrever usando instruções sem precisar bloquear cada uma. Eles serão descartados na ordem inversa quando o bloco de contenção for concluído.

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
fonte