Esiste un modello di smaltimento deterministico migliore rispetto agli "utilizzamenti" nidificati?

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

  •  09-06-2019
  •  | 
  •  

Domanda

In C#, se desidero ripulire in modo deterministico le risorse non gestite, posso utilizzare la parola chiave "using".Ma per più oggetti dipendenti, questo finisce per annidarsi sempre di più:

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.
        }
    }
}

In C++, sono abituato a poter utilizzare i distruttori per farlo in questo modo:

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

Esiste un modo migliore in C# per farlo?Oppure sono bloccato con i molteplici livelli di nidificazione?

È stato utile?

Soluzione

Non è necessario nidificare con più utilizzi:

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
}

Altri suggerimenti

Puoi mettere insieme le istruzioni using prima delle parentesi graffe aperte in questo modo:

  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

Potresti usare questa sintassi per condensare un po' le cose:

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

Questa è una di quelle rare occasioni in cui non usare { } per tutti i blocchi ha senso IMHO.

Invece di annidare utilizzando le istruzioni, puoi semplicemente scrivere manualmente le chiamate .Dispose, ma quasi sicuramente ne mancherai una ad un certo punto.

Esegui FxCop o qualcos'altro che possa garantire che tutte le istanze del tipo che implementano IDisposable abbiano una chiamata .Dispose() o gestiscano la nidificazione.

Ho implementato soluzioni come Michele PratiE' prima, ma suo StreamWrapper il codice non tiene conto se il file Dispose() i metodi chiamati sulle variabili membro lanciano un'eccezione per un motivo o per l'altro, la successiva Dispose()es non verranno chiamati e le risorse potrebbero penzolare.Il modo più sicuro affinché funzioni è:

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

puoi omettere le parentesi graffe, come:

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.
}

oppure utilizza l'approccio normale "prova finalmente":

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
}

Ciò comporta un vantaggio netto molto più ampio in righe di codice, ma un guadagno tangibile in termini di leggibilità:

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

Dove StreamWrapper è definito qui:

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

Con un certo sforzo, StreamWrapper potrebbe essere sottoposto a refactoring per essere più generico e riutilizzabile.

Va notato che generalmente quando si crea uno stream basato su un altro stream, il nuovo stream chiuderà quello passato.Quindi, per ridurre ulteriormente il tuo esempio:

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

per questo esempio supponiamo che tu abbia:

un file denominato 1.xml in c:\

una casella di testo denominata textBox1, con le proprietà multilinea impostate su ON.

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

L'istruzione using è zucchero sintattico che si converte in:

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

Puoi chiamare esplicitamente Dispose sui tuoi oggetti, ma non sarà altrettanto sicuro, poiché se uno di essi genera un'eccezione, le risorse non verranno liberate correttamente.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top