Existe um padrão de descarte determinístico melhor do que “usando” aninhados?

StackOverflow https://stackoverflow.com/questions/75722

  •  09-06-2019
  •  | 
  •  

Pergunta

Em C#, se eu quiser limpar deterministicamente recursos não gerenciados, posso usar a palavra-chave "using".Mas para vários objetos dependentes, isso acaba aninhando cada vez mais:

using (FileStream fs = new FileStream("c:\file.txt", FileMode.Open))
{
    using (BufferedStream bs = new BufferedStream(fs))
    {
        using (StreamReader sr = new StreamReader(bs))
        {
            // use sr, and have everything cleaned up when done.
        }
    }
}

Em C++, estou acostumado a usar destruidores para fazer assim:

{    
    FileStream fs("c:\file.txt", FileMode.Open);
    BufferedStream bs(fs);
    StreamReader sr(bs);
    // use sr, and have everything cleaned up when done.
}

Existe uma maneira melhor em C# de fazer isso?Ou estou preso aos vários níveis de aninhamento?

Foi útil?

Solução

Você não precisa aninhar vários usos:

using (FileStream fs = new FileStream("c:\file.txt", FileMode.Open))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
    // all three get disposed when you're done
}

Outras dicas

Você pode juntar as instruções using antes dos colchetes de abertura, assim:

  using (StreamWriter w1 = File.CreateText("W1"))
  using (StreamWriter w2 = File.CreateText("W2"))
  {
      // code here
  }

http://blogs.msdn.com/ericgu/archive/2004/08/05/209267.aspx

Você poderia usar esta sintaxe para condensar um pouco as coisas:

using (FileStream fs = new FileStream("c:\file.txt", FileMode.Open))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
}

Esta é uma daquelas raras ocasiões em que não usar { } para todos os blocos faz sentido, IMHO.

Em vez de aninhar instruções using, você pode simplesmente escrever as chamadas .Dispose manualmente - mas é quase certo que você perderá uma em algum momento.

Execute FxCop ou qualquer outra coisa que possa garantir que todas as instâncias do tipo que implementam IDisposable tenham uma chamada .Dispose() ou lide com o aninhamento.

Eu implementei soluções como Michael Pradosé antes, mas o dele StreamWrapper código não leva em conta se o Dispose() métodos chamados nas variáveis-membro lançam uma exceção por um motivo ou outro, o subsequente Dispose()es não serão chamados e os recursos poderão ficar pendentes.A maneira mais segura de isso funcionar é:

        var exceptions = new List<Exception>();

        try
        {
            this.sr.Dispose();
        }
        catch (Exception ex)
        {
            exceptions.Add(ex);
        }

        try
        {
            this.bs.Dispose();
        }
        catch (Exception ex)
        {
            exceptions.Add(ex);
        }

        try
        {
            this.fs.Dispose();
        }
        catch (Exception ex)
        {
            exceptions.Add(ex);
        }

        if (exceptions.Count > 0)
        {
            throw new AggregateException(exceptions);
        }
    }

você pode omitir as chaves, como:

using (FileStream fs = new FileStream("c:\file.txt", FileMode.Open))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
        // use sr, and have everything cleaned up when done.
}

ou use a abordagem try finalmente regular:

FileStream fs = new FileStream("c:\file.txt", FileMode.Open);
BufferedStream bs = new BufferedStream(fs);
StreamReader sr = new StreamReader(bs);
try
{
        // use sr, and have everything cleaned up when done.
}finally{
   sr.Close(); // should be enough since you hand control to the reader
}

Isso proporciona uma vantagem líquida muito maior em linhas de código, mas um ganho tangível em legibilidade:

using (StreamWrapper wrapper = new StreamWrapper("c:\file.txt", FileMode.Open))
{
    // do stuff using wrapper.Reader
}

Onde StreamWrapper é definido aqui:

private class StreamWrapper : IDisposable
{
    private readonly FileStream fs;
    private readonly BufferedStream bs;
    private readonly StreamReader sr;

    public StreamWrapper(string fileName, FileMode mode)
    {
        fs = new FileStream(fileName, mode);
        bs = new BufferedStream(fs);
        sr = new StreamReader(bs);
    }

    public StreamReader Reader
    {
        get { return sr; }
    }

    public void Dispose()
    {
        sr.Dispose();
        bs.Dispose();
        fs.Dispose();
    }
}

Com algum esforço, o StreamWrapper poderia ser refatorado para ser mais genérico e reutilizável.

Deve-se notar que geralmente ao criar um fluxo baseado em outro fluxo, o novo fluxo fechará aquele que está sendo transmitido.Então, para reduzir ainda mais o seu exemplo:

using (Stream Reader sr = new StreamReader( new BufferedStream( new FileStream("c:\file.txt", FileMode.Open))))
{
    // all three get disposed when you're done
}

para este exemplo, vamos supor que você tenha:

um arquivo chamado 1.xml em c:\

uma caixa de texto chamada textBox1, com as propriedades multilinhas ativadas.

const string fname = @"c:\1.xml";

StreamReader sr=new StreamReader(new BufferedStream(new FileStream(fname,FileMode.Open,FileAccess.Read,FileShare.Delete)));
textBox1.Text = sr.ReadToEnd();

A instrução using é um açúcar sintático que se converte em:

   try
   {
      obj declaration
      ...
   }
   finally
   {
      obj.Dispose();
   }

Você pode chamar Dispose explicitamente em seus objetos, mas não será tão seguro, pois se um deles lançar uma exceção, os recursos não serão liberados corretamente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top