This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book. Last time we looked at how you can import identifiers that happens to conflict with Pascal keywords. This post includes the section on class helpers.
Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.
"With a little help from your friends
When Borland started to port the Delphi compiler, RTL and VCL to the .NET platform, they soon faced a problem. While the object model and naming conventions of the FCL classes and methods were strikingly similar with native Delphi, some common methods (such as Free and ClassName) were missing from the .NET classes. Basically, Borland had three options;
- Create a shadow class hierarchy (where TObject inherits from System.Object)
- Alias FCL classes, ignore missing methods, break lots of existing code
- Invent something to close the gap
Not content with becoming a shadow, and not willing to break everybody’s existing, clean object oriented code, they decided to invent something. And they invented class helpers. A class helper is a compiler trick used to inject new members into an existing class and all its descendants.
With this solution TObject is defined as a type-alias for System.Object and it has an accompanying class helper, TObjectHelper, which injects the missing methods into TObject and all its descendants. The effect is that the Free and ClassName methods are now available for all Delphi and .NET classes. Similar tricks have been done to implement VCL for .NET classes such as TPersistent, TComponent and Variant.
To define a class helper you use the syntax
TMyClassHelper = class helper(TBaseClassHelper) for TExternalClass
where TBaseClassHelper is the name of an optional class helper that you inherit from. This is useful when you want to help an already helped class.
A class helper can contain instance methods, class methods and class fields, but you cannot add instance fields. This limitation can be circumvented by using a class var HashTable keyed by the Self instance implicitly received by the class helper methods. In some cases you can reuse the helped object's general storage mechanism. For instance, the TComponentHelper uses the Site property of Component to store the per-component properties Tag, Components and Owner.
Here is an excerpt from the ClassHelpers project that shows all the different kind of members that can be injected.
TMyClassHelper = class helper(TObjectHelper) for TExternalClass
class constructor Create; overload;
constructor NewConstructor(const AName: string);
procedure NewVirtualMethod; virtual;
procedure NewDynamicMethod; dynamic;
class procedure NewClassMethod;
class procedure NewVirtualClassMethod; virtual;
class procedure NewClassStaticMethod; static;
property NewProperty: integer read GetNewProperty write SetNewProperty;
class property NewClassProperty: string read FNewClassVar
Caution: In general, class helpers can be useful to close the gap between different platforms or component sets, but should normally not be used as a design element. If you have full control of a class, you should not inject methods into it by using a class helper; you should change the class itself (or derive from or aggregate it).
Update: C# has since (in the 3.0 beta version) adopted a similar technique called Extension Methods.