Class helpers (introduced in Delphi 2007 2006 2005 – thanks to Chris and Bruce for the corrections) seem to be cropping up more and more frequently in suggested work-arounds or implementation approaches. I find this worrying given that this language feature has always come with the admonition from CodeGear that it isn’t advisable to use it!
So why do people seem so keen on using them, and why shouldn’t they?
Class helpers are to be considered “bad” when used in an application because they were never designed for general purpose use. As I understand it, they were devised to workaround a problem (or problems) in the VCL that existed for the language (or more accurately, the VCL) implementors, not those who implement code that merely uses the VCL.
The biggest problem with class helpers, from the point of view of using them in your own applications, is the fact that only one class helper for a given class may be referenced at any time.
That is, if you have two helpers for a class nominally reachable in your code (e.g. in two separate units both of which are in your uses list), only one of them will actually be “seen” by the compiler. You won’t get any warnings or even hints about any other helpers that exist or may be hidden.
So you can be happily coding away with your lovingly crafted helper, and then you add some other unit to your uses list that unbeknownst to you contains another helper for the same class and suddenly your code simply stops compiling.
Since helpers are used – as designed – within the VCL, adding your own helpers for VCL classes is likely to lead to this problem if you also write code that (perhaps unknown to you) relies on the VCL helpers for those classes.
A Sticky Situation
You may be able to fudge your way out of this predicament by fiddling with the order of your units in the uses list (once you’ve figured out what’s going on, which is likely to come after perhaps your 2nd or 3rd restart of the IDE and an experimental reboot or two before you accept that it isn’t just the compiler having “a moment”).
If you can’t fix it with the uses list – if more than 2 helpers are involved – you might perhaps succeed in moving one or other helper up or down the ancestry of the helped class, assuming you are in control of the source for that helper and hoping that that does not in turn create a conflict with some other helper out there, perhaps in some other area of your code.
But if you can’t wriggle your way out of the situation then you are well and truly stuck, because you can’t even use qualification to forcibly reference the “hidden” helper.
Won’t compile if TMyHelper is “hidden” by some other helper for the class of obj involved. In fact, this code won’t compile anyway because TMyHelper does not exist as a “proper” type, so you can’t even use this style to aid clarity if you wish (i.e. to make it clear that your code is reliant on a helper).
But Without Class Helpers, What Can We Do?
Fortunately there is an alternative, which is not only immune from all these problems but is also in my opinion actually more flexible/powerful (not least because it works in any Delphi version).
That is, to use explicit helper-style pseudo sub-classes, the “old fashioned” way, where you adhere to the rules for helpers (only accessing public methods, not adding instance data etc etc) but use explicit hard-casting to “add” your methods to an instance.
TMyHelper = class(TForm) procedure MyMethod; end; TMyHelper(form).MyMethod;
This is “better” to my mind because:
a) it is explicit. No magic. No wondering where these undocumented methods came from (for the uninitiated, unaware of the existence of your “helper” class in some obscure unit somewhere).
b) it is unbreakable. As long as you stick to the rules for implementing the helper itself, nobody else’s helper can “hide” yours or interfere with how it works.
c) it is more powerful and more flexible – the “helper” subclass is able to access protected as well as public members.
There are three downsides – two of which are arguably in fact a further benefit, in some situations at least.
1) it is necessary to explicitly type-cast in order to invoke your helper functionality.
2) the compiler will not prevent you from violating some of the rules of class helping (not overriding inherited methods). It still isn’t possible to access private members, for example.
3) protected members are made visible in “consumer” code.
It is the first and last of these that I could be considered further benefits.
In the case of the first, it is made clear that an instance is being treated as something that, strictly speaking, it isn’t. To my mind the real basis for this complaint is that it involves a little more typing than using a formal helper.
In the case of the third, gaining access to protected members is quite often the very reason that such a class might have been used in the first place, since some aspects of frameworks – particularly in the case of the VCL – keep some members protected that would most usefully be made public.
For example, TControl introduces the Color property but only as a protected member, so if you have only a TControl reference to some control whose Color you wish to change (or inspect) you need some way to bring that protected member into visibility. You could have a complicated type checking and type casting sequence (hoping you catch all the relevant sub-classes), or you could use a “helper”:
if ctrl is TEdit then TEdit(ctrl).Color := aColor else if ctrl is TListbox then TListbox(ctrl).Color := aColor else....
TControlHelper = class(TControl); TControlHelper(ctrl).Color := aColor;
Note that this is not even possible with a “proper” class helper, so if you are using a “proper” class helper you will still need this “dirty” alternative to solve this particular problem.
Why not simply consistently use one approach that meets all needs?
But It’s Not Type Safe! TWorld.IsEnding!
Type safety is a potential concern of course, with the risk that you may cast to a helper class inappropriate to the object being cast, but this is not a problem unique to class helpers and you can easily code safely with this technique if you prefer:
TControlHelper(obj as TControl).Color := aColor;
Personally I feel the slight risk (which it is in my power to mitigate and avoid) is worth the comfort of knowing that some-one else’s helper (or perhaps even an ill considered additional helper of my own) is not one day going to break my code.
So Why Bother WIth Class Helpers At All?
Good question. It is rather curious that the VCL problem was not solved using this alternate approach, for example. Why spend time implementing a seemingly flawed language feature to solve an problem that can be easily solved some other way?
I suspect that class helpers might be earmarked for future things… they bear an uncanny resemblance to extension methods in C#, for example, which are key to the syntactic candy floss and lexical gymnastics required to deliver LINQ.
But until their purpose is fully realised, I personally think it wise to take heed of CodeGear’s advice and steer well clear.