Hack #4: Access to protected methods
As we saw earlier, it is possible to gain write access to a read-only property, or rather to its backing field (am talking about native code Delphi here, Win32 and Kylix, - the situation is quite a bit stricter and cleaner in .NET land). You can promote protected properties to public or published to gain access to them, but there is no corresponding promotion syntax for fields or methods. Often you absolutely need to call a protected method of some class - most often method in a VCL component or a 3rd party utility class. This is usually the sign of a badly design class. The author didn't think that application level code would need access to the method. A mock-up example would be:
unit Utility;
interface
type
TUtility = class
protected
procedure Useful;
public
constructor Create;
procedure Boring;
end;
We want to call the protected Useful method, but all we got access to is the public Boring method. Typical, right? ;) If you are lucky, you have control over the type of the instance you are using, and can write your own descendent that promotes the method the clean way, like this:
type
TMyUtility = class(TUtility)
public
procedure Useful;
end;
procedure TMyUtility.Useful;
begin
inherited Useful;
end;
Now you can use TMyUtility instead of TUtility and you'll have the Useful method available as a public method. A final twist is to use the same class name, like this:
unit MyUtility;
interface
uses
Utility;
type
TUtility = class(Utility.TUtility)
public
procedure Useful;
end;
Now you only need to add the MyUtility reference in the uses clause after the Utility reference. However, sometimes you cannot control the type of the instance used - it is created inside a body of 3rd party code, for instance.
As many experienced Delphi programmers will know, you can easily gain access to the protected members of a class instance, simply by casting the instance to a descendent class type declared in the same unit as the cast-expression. For instance, to call the protected Useful method of an instance of the TUtility class you can just:
type
TUtilityEx = class(TUtility)
end;
procedure Foo(Instance: TUtility);
begin
// Fails to compile, cannot call protected method
Instance.Useful;
// compiles fine, calls protected method
TUtilityEx (Instance).Useful;
end;
The way the Delphi visibility directives work, all code in the same unit as the declared class will gain full public access to all members. It is a kind of implicit friend concept as known from C++.
Thanks to the ingeniousness of Borland's compiler developers this common hack even works in .NET with Delphi 8 - as long as the caller and method is in the same assembly - it does not work across assemblies, because protected maps to the family-or-assembly method attribute in the CLR.
One very prominent component vendor even routinely hides away truly useful (and sometimes essential) methods of their component and helper classes into the protected section, seemingly only in an attempt to hide complexity from naïve users. Then in all their own units that need access to the methods, they turn around and perform this local-descendent-cast hack and their own classes. Shudder! This hack should only be used to get access to other people's code, and not used between your own units!
Interestingly, the compiler only uses the local hack-class to get access to the protected method. Although the TUtilityEx class is mentioned in the source code, nothing from that class (the VMT, RTTI, classname info etc.) is needed or referenced in the compiled code, so the smart linker removes it from the executable code. Otherwise this hack would be very expensive with regards to code-size.
Thanks again, Danny & co!
8 comments:
Is there a reason properties aren't used to expose protected methods and fields instead of this class hack?
Not sure what you mean here, Chris?
How do you expose "procedure Useful;" as a property?
As I show in the blog, if you can afford to use a instnace of a child class instead of the base class, you can promote the method by declaring a new public method with the same name, that simply forwards the call using the inherited syntax.
Only when you cannot change the type of the instance (it has already been created by OPC (Other People's Code)), you have to resort to the casting hack.
Hallvard, I think I was implying a wish more than anything.
Plus I confused field access and method access. I could have sworn C# allowed method access from a property accessor. Apparently not :-x
I like to rewrite a language, especially when I'm tired.
Thanks for coming forward, David...! :)
btw, it was not TeeChart and your company I was thinking about when I wrote that...
> advocate for more proper design
hear hear!
I very much agree with you, Petr. Maybe there needs to be a new visibility directive 'strict public', that is only available to programmers programming in advanced mode...? I'm only half-kidding.... :)
Remember that you can also use the strict keyword. strict private only allows access to methods/fields within the class, with strict protected you can only use methods/fields in descendants. Strangely there is no strict public/published ;-).
Also you can use the sealed keyword to prevent inheritance.
Alister,
Indeed!
I cover the relatively new strict, sealed and abstract classes and final methods here:
http://hallvards.blogspot.com/2006/09/dn4dp2-protecting-your-privates.html
Post a Comment