Reputation: 2205
What happens if you instantiate an object that implements IDisposable during a method call?
For example
return MyMethod(new MyIDisposableObject());
Will the Dispose method of MYIDisposableObject ever get called?
Ok, so if I have the following code in MyIDisposableObject will the IDBConnection get closed and disposed of properly or is it still not safe?
protected IDbConnection _db;
bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~MyIDisposableObject()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
// free other managed objects that implement
// IDisposable only
if (_db != null)
{
_db.Close();
_db.Dispose();
}
}
// release any unmanaged objects
// set the object references to null
_db = null;
_disposed = true;
}
Upvotes: 1
Views: 334
Reputation: 81105
Unlike C++, where the lifetime an object and the cleanup of its resources are closely connected, in .NET they are largely detached. Objects in .NET live as long as the system "knows" about them, or a reference to them is held in some other object the system knows about. Objects cease to exist when the last reference to them is overwritten. Although the system keeps "hidden" references to certain objects (e.g. it has a list of all objects which have registered Finalize
methods), in many cases the only evidence that some particular object ever existed will be a user-code reference to that object. If there is e.g. a 1024-byte range of memory which is not used by anything the GC knows about, the GC will neither know nor care whether that space had been held by sixteen 64-byte objects, a dozen 84-byte objects and a 16-byte object, or some other combination of objects.
This approach works very well for managing memory. The one problem with it comes when objects ask other entities to do things (like grant exclusive access to a file) until further notice. If an object which asks for exclusive access to a file simply ceases to exist without letting anyone know such access is no longer needed, the file will by needlessly inaccessible to everyone else. The IDisposable
interface resolves this problem, somewhat: an object whose Dispose
method is called should notify every entity that has asked to do anything its behalf until further notice, that it has no further need of such services.
The key to properly using IDisposable
is to ensure that every object which requires cleanup has at any given time exactly one "owner". That owner should either be a local variable which is guarded via using
or try
/finally
block or a field of an object which implements IDisposable
, and which will Dispose
the field when its own Dispose
method is called. If a method with a local variable which owns an IDisposable
returns that variable without having called Dispose
, then ownership will be transferred to the caller of the method. Note that C# has no linguistic construct to recognize ownership except when an object which is created within a method will not be needed after the method returns (a using
block handles that case nicely). Otherwise, it is necessary for programmers to keep track of object ownership manually. Failure to do so won't cause compilation errors, but will often cause programs not to work, or to leave outside entities waiting needlessly for notice that their services are no longer required.
The case of return new MyIDisposableObject();
doesn't quite meet either of the above stated patterns, but is nonetheless acceptable because if it were guarded by a try/finally, it would look like:
bool ok = false;
MyIDisposableObject ret = null;
try
{
ret = new MyIDisposableObject();
ok = true;
return ret;
}
finally
{
if (!ok && ret != null)
ret.Dispose();
}
The only way the ret.Dispose()
statement could execute would be if an exception occurred sometime between the store to ret
and the following return. If code is written as return new MyIDisposableObject();
, there's no way an exception can occur there.
Your code, however, is different, since you add another function call. That pattern is only safe, however, if MyMethod
promises to either return an object which encapsulate the passed-in object, or Dispose
it if unable to do so because of an exception. Since that is generally not the case, depending upon whether MyMethod
is supposed to return an object which encapsulate the passed-in reference, the correct pattern would either be:
using (var myObject = new MyDisposableObject())
return MyMethod(myObject);
if the MyDisposableObject
will not be encapsulated in the object returned by MyMethod
, and will thus have no further use once it returns, or else
MyIDisposableObject innerObject = new MyDisposableobject;
try
{
var ret = MyMethod(innerObject);
innerObject = null; // Note that `ret` still has an encapsulated reference to it
return ret;
}
finally
{
if (innerObject != null) // Reference wasn't yet encapsulated in `ret`
innerObject.Dispose();
}
Note that if the call to MyMethod
succeeds, the innerObject
variable will be cleared but the object won't told to give outside entities notice to discontinue their services, since the calling code will need the object returned by MyMethod
, which will in turn need innerObject
, which will in turn need those outside entities' services. If the call to MyMethod
throws an exception, then the innerObject
variable will not be cleared, and the finally
block code will thus know that it holds the only reference to innerObject
, that reference is about to vanish, and thus no other code will ever use innerObject
. Consequently, the finally
block needs to ask innerObject
to immediately notify outside entities their services are no longer required; , if it doesn't do so nothing else will.
Upvotes: 0
Reputation: 20157
Unless MyMethod
calls the Dispose()
method of its parameter, no, it will not. And it's not a great pattern to do. Let the code which owns the resource dispose the resource. Your code should be more idiomatically written as:
using (var o = new MyIDisposableObject())
{
return MyMethod(o);
}
Upvotes: 0
Reputation: 181
Not by default. Here's a demonstrative sample. You will see that the Cat never got disposed.
class Program
{
public static void Main(string[] args)
{
SayCatName(new Cat() { Name = "Whiskers" });
Console.Read();
}
public static void SayCatName(Cat c)
{
Console.WriteLine(c.Name);
}
}
public class Cat : IDisposable
{
public string Name { get; set; }
public void Dispose()
{
Console.WriteLine("Cat was disposed");
}
}
Upvotes: 0
Reputation: 887255
Dispose()
is a normal method.
Like any other method, it will not be called unless you write code that calls it.
For example, the using()
statement generates code that calls the Dispose()
method.
Note that classes that own native resources are supposed to have a finalizer that calls Dispose(false)
to release them (see the Dispose()
pattern).
The finalizer will run once the object is GC'd (which may never happen)
Upvotes: 3