views:

334

answers:

6

Sometimes I need to use several disposable objects within a function. Most common case is having StreamReader and StreamWriter but sometimes it's even more than this.

Nested using statements quickly add up and look ugly. To remedy this I've created a small class that collects IDisposable objects and disposes of them when it itself is disposed.

public class MultiDispose : HashSet<IDisposable>, IDisposable
{
    public MultiDispose(params IDisposable[] objectsToDispose)
    {
        foreach (IDisposable d in objectsToDispose)
        {
            this.Add(d);
        }
    }

    public T Add<T>(T obj) where T : IDisposable
    {
        base.Add(obj);
        return obj;
    }

    public void DisposeObject(IDisposable obj)
    {
        obj.Dispose();
        base.Remove(obj);
    }


    #region IDisposable Members

    public void Dispose()
    {
        foreach (IDisposable d in this)
        {
            d.Dispose();
        }

    }

    #endregion
}

So my code now looks like this:

        using (MultiDispose md = new MultiDispose())
        {
            StreamReader rdr = md.Add(new StreamReader(args[0]));
            StreamWriter wrt = md.Add(new StreamWriter(args[1]));
            WhateverElseNeedsDisposing w = md.Add(new WhateverElseNeedsDisposing());

            // code
        }

Is there anything wrong with this approach that can cause problems down the road? I left the Remove function inherited from the HashSet on purpose so that the class would be more flexible. Surely misusing this function can lead to objects not being disposed of properly, but then there many other ways to shoot yourself in the foot without this class.

+18  A: 

You could just do this:

using (var a = new A())
using (var b = new B())
{
    /// ...
}
ChaosPandion
I'll only add this comment once, even though three people have said the same thing. If you have stylecop as part of your coding standard, I don't think it will like this because it falls foul of the rule that bans if without curly braces
Stewart
Dang! That's what self-learning a language from IntelliSense does to you! I've used C# for years and had haven't thought of chaining using statements this way :(
Ghostrider
@Stewart if StyleCop doesn't like this StyleCop is wrong.
Will
@Stewart - That's a flaw in stylecop, though. Allowing using statements to "stack" like this is intentional; note how the IDE doesn't try to indent additional using lines.
Joel Coehoorn
Obeying Stylecop's (or FxCop's) every recommendation is a bit like following a GPS unit into a lake. There are just times you need to use your judgement (which software is incapable of doing.)
Josh Einstein
I never said stylecop was right - it was just an observation based on my own experience - just that if it is turned on in your code base the pragma to disable it is uglier than the original code :)
Stewart
The only time I'd see this as a StyleCop issue is if you run it as part of your build process and need to pass with 100% for whatever reason. Just add the suppression attribute. [SuppressMessage("Microsoft.StyleCop.CSharp.LayoutRules", "SA1503:CurlyBracketsMustNotBeOmitted", Justification = "SA1503 Chaining using statements to avoid nesting madness.")]
ParmesanCodice
@Josh, love the analogy about the GPS! +1
code4life
@ParmesanCodice - If something runs as part of your build process then it should always pass 100% - otherwise there is no point running it because you won't see the new warnings because of the old ones. On our team this wouldn't be allowed because it disables the check for if, for and while - this rarely seen case (I didn't know it was allowed until I saw this for sure) isn't worth breaking one of the core principles of our coding standard for. More than one using in a method is pretty rare anyway. Love the GPS analogy too though
Stewart
@Stewart You can attribute only the relevant methods, to limit the damage, but I agree with all your points. The only time I personally do something like this is with switch statements.
ParmesanCodice
+9  A: 

Maybe it is just that you have shown a simple example, but I think the following is more readable.

 using (StreamReader rdr = new StreamReader(args[0])) 
 using (StreamWriter wrt = new StreamWriter(args[1])) 
 {     
   // code 
 }
Chris Taylor
+5  A: 

You can make nested using statements prettier by only using one pair of braces, like this:

using (StreamReader rdr = new StreamReader(args[0])) 
using (StreamWriter wrt = new StreamWriter(args[1])) 
{
    ///...
}

To answer your question, you need to dispose in the opposite order of addition.
Therefore, you cannot use a HashSet.

Also, there is no reason to expose the list of IDisposables to the outside world.
Therefore, you should not inherit any collection class, and instead maintain a private List<IDisposable>.

You should then have public Add<T> and Dispose methods (and no other methods), and loop through the list backwards in Dispose.

SLaks
+1 great catch on the Hashset...
Josh Einstein
+3  A: 

Personally this would drive me nuts. If you are finding nested using statements to be annoying, you could revert to the try/finally syntax. Dispose methods are not supposed to throw exceptions so you could assume that multiple disposables would not need to be individually wrapped in try/finally blocks.

Also worth noting is that you only need one set of brackets for adjacent using blocks like:

using (var stream = File.Open(...))
using (var reader = new StreamReader(stream)) {

   // do stuff

}
Josh Einstein
+8  A: 

A few points about the general principle:

  • Your code is distinctly non-idiomatic C#. Basically you're asking anyone else who works with your code to take on board an unusual style for very little benefit.
  • As others have pointed out, you can nest using statements without extra braces
  • If you find yourself with lots of using statements in a single method, you might want to consider breaking it into smaller methods
  • If you have two variables of the same type, you can use a single using statement:

    using (Stream input = File.OpenRead("input.dat"),
           output = File.OpenWrite("output.dat"))
    {
    }
    

Now assuming you really want to go ahead with this:

  • Your code will dispose of its contained resources in a hard-to-predict order. Instead of using a set, it should embed a list - and then dispose of things in the reverse order to the calls to Add.
  • There is no reason to derive from HashSet<T> or indeed any collection. You should just have a list within the class as a private member variable.
  • If one of the Dispose calls fails, none of the other Dispose calls will be made; with a traditional using statement, each call to Dispose is made within its own finally block.

Basically, I think it's a bad idea. Nesting two levels deep is far from painful; nesting three should be rare; nesting four or more strongly suggests refactoring. Rather than trying to cope with the pain of deep nesting, you should design away from it.

Jon Skeet
+1 for 'bad idea'. My feelings go a bit farther in that implicit blocks, unless the code will never be touched again, are bugs waiting to happen. I always, without exception use explicit blocks. And as you say, if I am bothered by the nesting, I design away from it.
Sky Sanders
I'm not totally sure, but my understanding of using statements means that your example of two variables of the same type is unsafe. I'm pretty certain the two objects would not be able to depend on one another for a start because the construction order is probably non deterministic, but also if the second constructor throws, I am pretty sure the first object will not get disposed.
Stewart
+1  A: 

I've got to say I disagree with the people who want to do using statements one after the other like:

using (var a = new StreamReader())
using (var b = new StreamWriter())
{
 // Implementation
}

In my opinion, that's very unreadable - having any block of code that's not wrapped is just bad style, and may lead to problems unless all developers working on it are very careful.

I'd put that on par with something like:

if (someBoolean) DoSomething();
{
  // Some code block unrelated to the if statement
}

Technically it's not invalid, but it's awful to look at.

I agree that the MultiDispose concept is probably not the best idea, due to the fact that it's not an accepted pattern, but I definitely wouldn't go this route either. If you can't break things up into smaller chunks, then I'd suggest just living with the nested usings.

Joe Enos