tags:

views:

968

answers:

9

In C#, if I want to deterministically clean up non-managed resources, I can use the "using" keyword. But for multiple dependent objects, this ends up nesting further and further:

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++, I'm used to being able to use destructors to do it like this:

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

Is there a better way in C# to do this? Or am I stuck with the multiple levels of nesting?

A: 

The using statement is syntactic sugar that converts to:

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

You can explicitly call Dispose on your objects, but it won't be as safe, since if one of them throws an exception, the resources won't be freed properly.

Abe Heidebrecht
NOTE: incorrect. the object initialization takes place OUTSIDE the try, even if there is more than one object. using(X a=f(),b=f()){g(a,b);} is the same as X a=f(); X b=f(); try{ g(a,b); } finally { b.Dispose(); a.Dispose(); there's a difference for exception safety.
Aaron
+1  A: 

Instead of nesting using statements, you can just write out the .Dispose calls manually - but you'll almost certainly miss one at some point.

Either run FxCop or something else that can make sure that all IDisposable-implementing type instances have a .Dispose() call, or deal with the nesting.

Greg Hurlman
+28  A: 

You don't have to nest with multiple usings:

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
}
Kyralessa
You can do that!?
sectrean
I didn't realize you could do this. Awesome!
Eclipse
wow, amazing the number of things that developers still don't know
BlackTigerX
So that's how you do it in C#. I knew VB.Net could do it, but the syntax didn't transliterate well.
Joel Coehoorn
That's just as nested - each successive using is treated as a one-line code block - think of an if statement, sans { }. All you've gained here is avoiding hitting the TAB key.
Greg Hurlman
What definition of "nested" are you using here, Greg?
Kyralessa
Wow, I didn't know about this either
Charles Graham
I agree with Greg Hurlman, it's a formatting trick: like if, while, etc, 'using' controls a single statement, but you normally use it with a block of statements.
Mike Dimmick
Well, yes it's a formatting trick, but the point is that it's a helpful trick, and non-obvious to someone new to C#.
Eclipse
What?! Why wasn't I told this?!
WOPR
What's really going to bake your noodle later on is, you can do the same thing with if statements.
emddudley
+7  A: 

You can put using statements together before the opening braces like so:

  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

Bob
Thanks for the reference link.
Sam
+2  A: 

You could use this syntax to condense things down a bit:

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

This is one of those rare occasions where not using { } for all blocks makes sense IMHO.

JeffFoster
A: 

you can omit the curly braces, like:

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

or use the regular try finally approach:

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
}
pb
A: 

This makes for a much larger net plus in lines of code, but a tangible gain in readability:

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

Where StreamWrapper is defined here:

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

With some effort, StreamWrapper could be refactored to be more generic and reusable.

Michael Meadows
+1  A: 

I have implemented solutions like Walking Disaster's before, but his StreamWrapper code doesn't take into account if the Dispose() methods called on the member variables throw an exception for one reason or another, the subsequent Dispose()es will not be called and resources could dangle. The safer way for that one to work is:

public void Dispose()
{
    Exception ex = null;

    try
    {
        sr.Dispose();
    }
    catch (Exception e)
    {
        ex = e;
    }

    try
    {
        bs.Dispose();
    }
    catch (Exception e)
    {
        if (ex != null)
        {
            ex.InnerException = e;
        }
        else
        {
            ex = e;
        }
    }

    try
    {
        fs.Dispose();
    }
    catch (Exception e)
    {
        if (ex != null)
        {
            if (ex.InnerException != null)
            {
                ex.InnerException.InnerException = e;
            }
            else
            {
                ex.InnerException = e;
            }
        }
        else
        {
            ex = e;
        }
    }

    if (ex != null)
    {
        throw ex;
    }
}
Jesse C. Slicer
A: 

It should be noted that generally when creating stream based off another stream the new stream will close the one being passed in. So, to further reduce your example:

using (Stream Reader sr = new StreamReader( new BufferedStream( new FileStream("c:\file.txt", FileMode.Open))))
{
    // all three get disposed when you're done
}
Joel Lucsy
NOTE: exception safety FAIL. this will NOT close the FileStream if the BufferedStream constructor fails. using syntax is really tricky compares to C++ RAII.
Aaron