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 ans开发者_开发百科wers and discussion.
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:
'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.
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.
If DBHandle implements IDisposable then what you have should work.
There is no 'special' way of returning IDisposable.
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.
精彩评论