5

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.

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

5 Answers5

9

Hint: When returning a disposable object from your using-block, have in mind that the call to Dispose() is done when the return-statement is executed!!!

So an object returned from within a using-block will already be disposed when it comes out of the function.

See the following code for an example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class MyDisposable : IDisposable
    {
        public void DoSomething()
        {
            Console.WriteLine("  In DoSomething");
        }

        #region IDisposable Members

        public void Dispose()
        {
            Console.WriteLine("  In Dispose");
        }

        #endregion
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting Main\n");

            Console.WriteLine("Before NormalMethod");
            NormalMethod();
            Console.WriteLine("After NormalMethod\n");

            Console.WriteLine("Before ReturningMethod");
            MyDisposable m = ReturningMethod();
            m.DoSomething(); // Here the object already has been disposed!
            Console.WriteLine("After ReturningMethod\n");

        }

        private static void NormalMethod()
        {
            using (MyDisposable myDisposable = new MyDisposable())
            {
                Console.WriteLine("  In NormalMethod");
            }
            return;
        }

        private static MyDisposable ReturningMethod()
        {
            using (MyDisposable myDisposable = new MyDisposable())
            {
                Console.WriteLine("  In ReturningMethod");
                return myDisposable;
            }
        }
    }
}

This will produce the following output:

enter image description here

Knasterbax
  • 7,895
  • 1
  • 29
  • 23
3

'Using' is doing your try/catch work for you, just have the db.Open; using will guarantee that regardless of whether it throws, it will dispose your connection.

Ana Betts
  • 73,868
  • 16
  • 141
  • 209
  • Right, but I don't want to dispose `db` in the `GetDB` function if it doesn't encounter any exception. So, I can't use `using` in `GetDB`. – palswim Nov 18 '10 at 22:09
  • That's not how 'using' works. Instead, you want a factory that reuses its object as long as it's still valid. – Ana Betts Nov 18 '10 at 22:12
  • Actually, a `try..finally` would be better than a `try..catch` as the exception isn't handled, and incidentally that is exactly what the `using` block does. So the `using` block already does the work, and better. :) – Guffa Nov 18 '10 at 22:13
  • @palswim: You don't need a `using` in the `GetDB` method, or a `try..catch` either. Just get the connection, open it and return it. The `using` block that calls `GetDB` will always dispose the object. – Guffa Nov 18 '10 at 22:15
  • @Guffa: I guess I don't know the internal workings of `try..catch`. You're saying that, even if my function doesn't make it to the `return` line, the higher-level `using` statement will still be able to properly dispose of the object? (In my mind, it would create e `NullReferenceException` or something.) – palswim Nov 18 '10 at 22:22
  • @Guffa: Also, this sounds like another good answer for this question. – palswim Nov 18 '10 at 22:24
  • @palswim: you are trying carefully to ensure the successfully made object is available to be used elsewhere, but is disposed if it failed to open. That is exactly what the using clause does all by itself. When you compile a using statement, it is tranformed into a try/finally pair. So the part of your question which says `in other code: using (var obj = DatabaseObj.GetHandle() ) {...}` is all you need! If it succeeds, the `...` portion uses it. If it fails, the internal/hidden 'finally' clause will dispose of it for you. – Brent Arias Nov 18 '10 at 22:32
3

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.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
2

If DBHandle implements IDisposable then what you have should work.

There is no 'special' way of returning IDisposable.

freedomn-m
  • 27,664
  • 8
  • 35
  • 57
Jakub Konecki
  • 45,581
  • 7
  • 87
  • 126
1

The return value simply has to implement IDisposable.

In practical terms, this statement must be true:

IDisposable db = GetDB();

If that compiles, you can put GetDB() in a using statement.

Bryan Watts
  • 44,911
  • 16
  • 83
  • 88