Mat DeLong just posted another great example of when not to abuse class helpers in Delphi (though I should add that he didn’t seem to see it that way). 🙂
But you don’t need helpers to do what this technique achieves, and in my view you really shouldn’t be using helpers for it in the first place.
When hacking around in ways that you really shouldn’t, dressing it up in the respectability of a (mis-used) language feature is asking for trouble. Not to mention that once you introduce a helper you must forever live with your fingers crossed that you don’t have in, or in the future happen to bring into, scope some other helper for the target class (and indeed that any 3rd party code you use now or in the future doesn’t do likewise).
I also believe this is almost certainly a bug in helpers as you should surely only have access to protected members from a helper, not private and certainly not strict private. Or so I would have thought and expected. Bizarrely this expectation is seemingly reflected in the Code Completion suggestions when referencing “Self” in a helper implementation, but is not enforced by the compiler.
Very odd. But perhaps cross some other fingers against the day that this bug – if such it is – is fixed. Just in case.
However, there is an alternative that doesn’t rely on mis-appropriating language features.
We have always (since Delphi 1!!) been able to reach private members of other classes by simply creating an overlay class and type-casting. Using Mat’s example, to gain access to the fComponentList member of a TDSCustomServer:
TDSCustomServerCracker = class abstract(TComponent) // :begin DSCustomServer member decls: protected FDbxContext: TDBXContext; private FStarted: Boolean; FConfig: TDSConfiguration; FServerMethodProvider: TDSServerMethodProvider; FComponentList: TDBXArrayList; FHideDSAdmin: Boolean; // :end DSCustomServer decl // Expose private members with some additional property declarations public property ComponentList: TDBXArrayList read fComponentList; end;
Now in your code, when you need to dig inside a DSComponent, hard-cast using your cracker class:
theList := TDSCustomServerCracker(someServer).ComponentList;
This works because your “fake” cracker class has the same layout in memory of its declared instance data, so you can use your cracker class to expose the instance data of some other class instance by hard casting.
Yes, this is more work and yes it’s much, much nastier. But what we are doing here is nasty. In my experience, as uncomfortable as it may make us feel, it is far better to be open and honest about hack-o-logy when we have to resort to it. Tarting it up and casting a veil of respectability over our misdeeds is just a pretense; a sop to our own ego’s (“I’m a professional developer, not a hacker!”).
Writing such code that draws attention to itself by laying it’s hackology out for all to see is absolutely the way to go in my view. Your future self will thank you, if no one else. 🙂
But over and above all of that, this also has the added advantage of being able to happily co-exist with any legitimate “helper” that is in scope (or is brought into scope in the future).