August22

SqlBulkCopy Dispose part 2

OK, fair point.  According to a friend I didn’t make my case very well in the previous post on SqlBulkCopy and Dispose.

As he stated, yes, you can put it in a using statement. And it does implement IDisposable explicitly.  Doesn’t that mean that everything is done?

using(SqlBulkCopy bcp = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default))
{
// DO something
}

// OR YOU CAN DO THIS
SqlBulkCopy bcp = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default);

((IDisposable)bcp).Dispose();

Internally the using seems to get converted to call to the virtual IDisposable.Dispose method according to the IL code I have looked at for the code above.

As an alternative, you could cast it to an IDisposable and call Dispose.  The class does have IDisposable, but only exposes Dispose explicitly (through the cast above).  This is a bad practice in my opinion because most developers won’t look for it.  They will try .Dispose through Intellisense.  If it doesn’t work they assume they don’t need to clean it up.

Close is the same as Dispose?

No, no, no!  There are way too many API’s out there that treat them the same.  But they should not be.  Look at the ADO.Net spec it is pretty clear in stating that Close should close connections and attempt to ease server load.  But all the resources of the object should be maintained until Dispose is called.  Hence the need for both.

Normally I have told people that putting an object in a using is the same as calling .Close() and .Dispose() on the object.  But lets look at this case.  Here is the SqlBulkCopy code for the .Close method.

public void Close()
{
if (this._insideRowsCopiedEvent)
{
throw SQL.InvalidOperationInsideEvent();
}
this.Dispose(true);
GC.SuppressFinalize(this);
}

Hmm, Close calls the internal Dispose with a true setting indicating it is happening from the Dispose event!  That is not true.  But to make matters much worse it flags the object with Gc.SupressFinalize to prevent the object from ever being cleaned up.  So any further reference to this object will force it to be pinned in memory.

What if you can’t use using?

Putting the SqlBulkCopy object in a using is not always possible.  Sometimes you need to pass it to a function.  You can’t call Dispose on it (without casting it to IDisposable).  But the real problem is that calling .Close() DOES call Dispose internally.  All ado.net objects are supposed to support a notion of closing to release connections, but not resources.  Maybe you want to close the connection, and then later reopen it.  Or you need to still get some metadata from the object, and don’t want it to go away entirely – just close it.

Maybe after you are done working with the object you want to unlink your delegates for things like NotifyAfter.  You would be resurrecting the object by touching the event handler.  This is not expected behavior.

What’s the big deal?

I have heard this already a few times.  Who cares?  You know how it works, so just use it correctly. 

The big deal to me is that the breakage of patterns on Microsoft API’s leads to confusion of users, and to very subtle bugs that programmers will have a very hard time tracking down.

I really hate to see variances within API’s for things like this.  These behaviors are supposed to be standardized so programmers have known behaviors and patterns to follow.  The fact that I have been working with ado.net for over 10 years and didn’t think to cast the object back to IDisposable to clean it up until I looked in a decompiler pretty much proves my point.

Comments (1) -

23/08/2011 06:56 #

tobi

Yeah, nasty API. But the rest of the framework is really good. Except all of the legacy collections like MatchCollection not being generic and not directly supporting linq to objects.

But those are really minor inconveniences. .NET is such a luxury to use. Java APIs even have public fields... That tells me everything.

tobi

Comments are closed