6

I asked a question regarding returning a Disposable (IDisposable) object from a function, but I thought that I would muddle the discussion if I raised this question there.

I created some sample code:

class UsingTest
{
    public class Disposable : IDisposable
    {
        public void Dispose()
        {
            var i = 0;
            i++;
        }
    }
    public static Disposable GetDisposable(bool error)
    {
        var obj = new Disposable();
        if (error)
            throw new Exception("Error!");
        return obj;
    }
}

I coded it this way deliberately, because then I call:

using (var tmp = UsingTest.GetDisposable(true)) { }

Using the debugger, I notice that the Dispose method never executes, even though we've already instantiated a Disposable object. If I correctly understand the purpose of Dispose, if this class actually had opened handles and the like, then we would not close them as soon as we had finished with them.

I ask this question because this behavior aligns with what I would expect, but in the answers to the related question, people seemed to indicate that using would take care of everything.

If using still somehow takes care of all of this, could someone explain what I'm missing? But, if this code could indeed cause resource leak, how would you suggest I code GetDisposable (with the condition that I must instantiate the IDisposable object and run code which could throw an exception prior to the return statement)?

Community
  • 1
  • 1
palswim
  • 11,856
  • 6
  • 53
  • 77

6 Answers6

11

The reason it's never called is because of the way you allocate it. The "tmp" variable is never allocated at all, because the GetDisposable(bool) function never returns due to the fact that you threw an exception.

If you were to say instead the following,

using (var tmp = new Disposable())
{
    throw new ArgumentException("Blah");
}

then you would see that IDisposable::Dispose() does indeed get called.

The fundamental thing to understand is that the using block has to get a valid reference to the IDisposable object. If some exception occurs so that the variable declared in the using block does not get assigned then you are out of luck, because the using block will have no knowledge of the IDisposable object.

As for returning an IDisposable object from a function, you should use a standard catch block inside of the function to call Dispose() in the event of a failure, but obviously you should not use a using block because this will dispose the object before you are ready to do so yourself.

Gregory Higley
  • 15,923
  • 9
  • 67
  • 96
  • is correct. If the method that creates the IDisposable can fail before returning the reference, it is responsible for ensuring that dispose is called. – ScottS Nov 19 '10 at 00:26
4

Depending upon what semantics you want for GetDisposable, this is probably how I would implement it:

public static Disposable GetDisposable(bool error)
{
    var obj = new Disposable();

    try
    {
        if (error)
            throw new Exception("Error!");

        return obj;
    }
    catch (Exception)
    {
        obj.Dispose();
        throw;
    }
}
strager
  • 88,763
  • 26
  • 134
  • 176
3

This is because the tmp variable is never assigned. It's something you need to be careful of with disposable objects. A better definition for GewtDisposable would be:

public static Disposable GetDisposable(bool error)
{
    var obj = new Disposable();

    try
    {
        if (error)
            throw new Exception("Error!");
        return obj;
    }
    catch
    {
        obj.Dispose();
        throw;
    }
}

Because it ensures that obj is disposed.

Andy Lowry
  • 1,767
  • 13
  • 12
  • A slight improvement would be to copy your main object variable to a second one and null out the first one before returning the second, and then use a "finally" block rather than a "catch" to handle the disposal (if the variable is null, don't dispose it). This will ensure the exception shows up as having occurred at the proper spot, not at the rethrow. – supercat Jan 21 '11 at 00:04
1

The IDisposable interface simply guarantees that the class that implements it has a Dispose method. It does nothing in regard to calling this method. A using block will call Dispose on the object when the block is exited.

Mark Avenius
  • 13,679
  • 6
  • 42
  • 50
  • 1
    There is one situation where `IDisposable` is optional, but if implemented will cause `Dispose` to get called automatically: if the return type of the `GetEnumerator` function called by a C# `foreach` or vb.net `For Each` statement implements `IDisposable`, or if the return type is `IEnumerator` and the instance it returns implements `IDisposable`, the compiler will call `IDisposable.Dispose` after enumeration is complete. – supercat Jan 26 '13 at 22:16
  • @supercat: that is good to know; I did not realize that this was the case. – Mark Avenius Jan 28 '13 at 15:58
  • It's a bit tricky. If the return type is a *class* which does not implement `IDisposable`, but `GetEnumerator` returns a derived class which does implement `IDisposable`, the `Dispose` method won't be called, but if it's an interface like the non-generic `IEnumerator`, the compiler will figure that the returned object might implement `IDisposable` and check for it at run-time. An ugly design, but since `Dispose` was omitted from `IEnumerator` there's not really any alternative. – supercat Jan 28 '13 at 16:19
1

You create an IDisposable in GetDisposable but since you exit the function by throwing an exception, it never gets returned and hence tmp never gets assigned. The using statement is shorthand for

var tmp = UsingTest.GetDisposable(true);
try { }
finally
{
    if(tmp != null) tmp.Dispose();
}

and you never reach the try block. The solution in your example is to check the error flag before creating the disposable obj:

public static Disposable GetDisposable(bool error)
{
    if (error)
        throw new Exception("Error!");
    return new Disposable();
}
Lee
  • 142,018
  • 20
  • 234
  • 287
0

A related question is Handling iDisposable in failed initializer or constructor and I think the answer is that if you want to avoid leaking disposable objects from a failed constructor, you'll have to smuggle out a copy of the object from the constructor (e.g. stash it in a passed-in container, or assign it to a passed-by-reference variable) and wrap the constructor call in a catch block. Icky, but I don't know how to do it better. VB.net can actually manage a little better than C# because of how its initializers work.

Community
  • 1
  • 1
supercat
  • 77,689
  • 9
  • 166
  • 211