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).
Tags: class helpers, hacks, language
-
Class helpers are just wannabe extension methods. And like extension methods you have full access to the type you are extending. So I highly doubt accessibility of private members is a bug and will ever change.
Also you can have as much class helpers as you want. You just cannot use more than one in the same scope!
There is also some weirdness with inheriting from some class that has an class helper because then RTTI is generated for the class helper method or something like that – I cannot remember exactly.
And you really suggest using some ugly hack that relies on memory layout and will blow up whenever I change any field in this or some parent class at runtime instead of compiletime? Just wow…
-
I agree that the current implementation of class helpers are a hack.
Nonetheless the concept of extension methods (you might read about it on msdn if you are not familiar with them) is something great and can give you great benefits for clean and decoupled code. Of course on the other hand power does not come without responsibility but it is always up to the user how to use his tools. It’s not the tool that is evil, it’s what you do with it.
That being said I really hope we will see true extension methods in Delphi (also for interfaces and maybe even simple types) soon. It actually is not something difficult – it requires a bit of compiler magic and that’s it. It’s important to understand how they work in C# because actually an extension method cannot override an instance method while class helpers can hide instance methods which is one of the dangerous things about them.
But even among the Java and C# people there are some that don’t like them and call them evil. So I guess its just a matter of taste.
-
Nice post!
I’d like to point out my blog post you mentioned starts off by saying: “I should start off by saying that with great power comes great responsibility. You can use this tip to gain access to private or strict private members of a class in another unit. But should you?”I was just letting people know they *could* do it. Should they? Probably not. Your hack isn’t really “better”, and if the class being hacked adds another private field, it will mysteriously stop working. Not that the approach I suggested is any better… just making sure one doesn’t come across as more glamorous than the other.
Again, great post!
-
The advantage of class and record helpers over extension methods is that you can add anything but fields to a class or record
Extension methods are just that: instance methods added to the type. No properties, no class methods.
Both are not the nicest language enhancements I know of, but they do serve interesting purposes.
-
Comments are now closed.


DelphiFeeds
14 comments