Esiste un modello di smaltimento deterministico migliore rispetto agli "utilizzamenti" nidificati?
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?
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
}
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.