Search code examples
c#idisposableusing-statement

Return a Disposable object for use in a using block


How do I return a disposable object in my function to ensure that it works properly within a using block? In my function, I want to act on the disposable object and also account for errors, which complicates this.

I have something similar to the following code so far:

DBHandle GetDB()
{
/*  // I can't do this, because the using block would dispose of my object within this function
    using( var db = DatabaseObj.GetHandle() )
    {
        db.Open();
        return db;
    }
*/
    var db = DatabaseObj.GetHandle();
    try
    {
        db.Open();
        return db;
    }
    catch (Exception ex)
    {
        db.Dispose();
        throw ex;
    }
}

// In other code:
using( var obj = GetDB() ){ /* ... */ }

Edit: Posted a more general question similar to this so as to not confuse answers and discussion.


Solution

  • You've got the right approach, but seem a bit lost as to how it's right.

    Consider the code that you (correctly) say can't work:

    DBHandle GetDB()
    {
        using( var db = DatabaseObj.GetHandle() )
        {
            db.Open();
            return db;
        }
    }
    

    This code is pretty much equivalent to:

    DBHandle GetDB()
    {
        var db = DatabaseObj.GetHandle();
        try
        {
          db.Open();
          return db;
        }
        finally
        {
            if(db != null)//not included if db is a value-type
              ((IDisposable)db).Dispose();
        }
    }
    

    A few things of note here include that the try doesn't happen until after the assignment (the same is true of using - it doesn't save you from exceptions prior to the assignment in the using) and that db is cast to IDisposable meaning both that it can't compile if that assignment isn't valid, and also that Dispose() can be either implicitly or explicitly implemented and this will work either way.

    Now of course, finally blocks will execute whether an exception occurs or not. You can't use using because it's equivalent to a finally and you want to Dispose() in your method only if an exception occurs. Hence you take the finally and turn it into a catch:

    DBHandle GetDB()
    {
        var db = DatabaseObj.GetHandle();
        try
        {
          db.Open();
          return db;
        }
        catch
        {
            if(db != null)
              ((IDisposable)db).Dispose();
            throw;
        }
    }
    

    This is pretty much the same as you have, except for the addition of a null check (maybe you can rule out the need for it) and that I'm using the bare throw (it's generally a good idea when you are going to re-throw an exception without altering or examining it. In some cases throwing a new exception is better, in which case you should include the original as the InnerException property of that new exception, so as to provide further information to someone debugging).

    So all in all, you were on the right track. Hopefully I've helped explain why.