Friday, November 21, 2003

The rules have not changed...

Let's hear it for Garbage Collection! Hip-Hip-Ho.... whoa there Nellie!! When it comes to VCL for .NET and Garbage Collection, the more things change, the more they stay the same..

The level of source-code compatibility between the traditional VCL/Win32 and VCL/.NET is nothing short of amazing. If you witnessed the Product Address at this year's Borland Developer Conference, you would have seen a demo where the original FishFact database demo was opened in Octane (Delphi 8 for .NET), compiled and ran with 0 (zero), count them, 0(zero) code changes. Not even the uses lists needed to change (remember WinTypes, and WinProcs? ;-).

OK, so they're compatible... What does this have to do with Garbage Collection? Well... there are some myths around GC that may get you in some serious trouble. Yes, it is true that in a GC environment, you generally don't have to worry about freeing memory. However there are a lot more types of resources than simply memory. Things like file handles, GDI handles, etc... It is easy to understand that these items are out of the reach of the GC, so you have to continue to guard your code with try..finally blocks to open/close, allocate/release, etc.. What is more subtle, though, are those "resources" that don't quite "fit" the definition of an "object."

So what other kinds of "resources" out there have to be managed in a similar fashion to those more traditional notion of a resource? I use the term "resource" loosely here in order to simply highlight the correlation to what one normally thinks of as a "resource." Suppose you have a class of objects that have specific knowledge of the container in which they live. When you create an instance of one of these object classes, they are given the container and proceed to toss themselves into that container. You now have this "bag" of objects by referencing the container. You can pick each item from the bag perform some operation on them. One suich operation that is common is to "destroy" the item. Well, since this item knows about its container, it plays nice and removes itself from the container before it self-destructs.

In the GC world, a common mistake is to simply think of "destroying" an object is simply freeing its memory. So I don't have to do that in the GC world because that will be handled for me, right? Sure it will.. eventually. But it will not automatically take care of one important resource, it's container's reference. That reference needs to be cleaned up. It is not a "resource" onto which the item itself holds, but is a resource nonetheless. When you look at the declaration of the object, you won't find a reference to this resource... oh wait yes you do... It is the reference to the container itself. It is through this reference that the "resource" lives.

So if you don't "destroy" an object, how do you make it release its references? In .NET, there is a simple pattern that is followed. An object that needs to do immediate resource cleanup, should implement the IDisposable interface. It has one method called procedure Dispose; (or for you that prefer a more "curly" world void Dispose();). Now when you want to "destroy" an item in the container, you cast the item to IDisposable and call the Dispose method. In the Dispose method it just performs the nessesary steps to remove itself from the container.

Sure, you could manually remove the item from the container yourself and forgo the IDisposable riggamarole, but what if you hand the item off to someone that has no specific knowledge of the particular container instance in which the object is currently living? This is why the IDisposable pattern is important.

So let's bring all this around now to Delphi 8 for .NET and its view of the world. All this IDisposable stuff seems tedious and would require some massive changes to my code to play nicely in .NET. This is where Delphi has realized some of the benefit of arriving a little later to the party. Delphi introduces an interesting language construct called "class helpers." There could be a whole tome written about the subject of "class helpers", but suffice it to say, they have given the Delphi programmer a familiar world in which to work. All that code you have written in which you dutifully have implemented your destructors to properly clean up after yourself, release resources, and generally keep your house in order can now continue to live on..and serve a real purpose beyond simply releasing memory. Releasing memory is no longer the responsibility of the lowly Destroy destructor. It now is a cue to the Delphi compiler that you want it to implement the IDisposable pattern for you. Your object now implicitly implements IDisposable and the Dispose method maps to your Destroy destructor. It even takes care of guarding your destructor from being called more than once by implementing the pattern of setting a compiler generated instance variable to true the first time through the Destroy destructor and exits immediately on all subsequent calls. To the world outside Delphi, your objects simply appear to implement the IDisposable pattern. The really cool thing though, is that all objects that come in from outside Delphi (remember all objects in .NET descend from System.Object, which is not Delphi's System.TObject. In the CLR world, it is the other way around. Borland.Delphi.System.TObject = System.Object) "appear" to have a non-virtual method called Free (thank you class helpers). If you are a seasoned Delphi developer, you know that in order to maintain proper "exception safety" you never call the Destroy destructor directly. This is because the Free method would check to make sure Self (or this) is non-nil before calling Destroy. In .NET it continues to do this as well, but now it checks if Self implements IDisposable and then calls Dispose if it does. This is why you can call MyHashTable.Free. Through the magic of class helpers, the Free method appears to be a method of HashTable.

So now all your nicely done try...finally code continues to serve a purpose, in fact is remains critical to the proper operation of VCL code. For instance, a TComponent derivitive takes an Owner parameter in its constructor. It then proceeds to add itself to the Owner's list of Owned Components. So, when you call Free on the component, the expectation is that that component is removed from the Owner's list immediately. These semantics are now preserved.

Garbage collection certainly takes away certain programming chores, but it is not a panacea. The more you understand the environment in into which you are moving, the better you'll be able to migrate your existing code. So the upsot of all this is that you don't have to change nearly as much code as I'm sure you originally thought nor should you.