Suppose we have a classes with method which is potentially very useful, but not available due protected scope:
unit Sealed;
interface
type
TGeneral = class(TObject)
{ this method is useful, but not available }
protected procedure Useful; virtual;
end;
TSpecific1 = class(TGeneral)
{ some descendants override `Useful` method }
protected procedure Useful; override;
end;
TSpecific2 = class(TGeneral)
{ and some dont, but inherit `Useful`ness from the parent }
end;
I know two old-school ways to reach out for such method, both are involving an inheritance and typecast. Both approaches should works the same with the basic case #1 and advanced polymorphic case #2.
program CallingSite;
uses Sealed;
function GetInstance: TGeneral;
begin
{ !PSEUDO! makes compiler happy about the rest of code }
// depending on use case supposed to return an instance of `TGeneral`
// or any of its descendants - `TSpecific1`, `TSpecific2`
end;
type
{ this makes a current module a "friend" for `TGeneral` }
TFriend = class(TGeneral)
end;
procedure Case1;
var
{ holds an instance of `TGeneral` }
General: TGeneral;
begin
General := GetInstance;
{ protected method is available for "friend" via static cast }
TFriend(General).Useful; // compiles!
end;
type
TIntroducer = class(TGeneral)
{ this "reintroduces" `Useful` method to public scope }
public procedure Useful; override;
// this approach ought to work even with strict protected methods
// !!! but I THINK it is UNSAFE to use on virtual and/or dynamic methods
end;
procedure TIntroducer.Useful;
begin
{ and calls `Useful` via wrapper }
inherited;
end;
procedure Case2;
var
{ polymorphic instance of any `TGeneral`'s descendant }
Specific: TGeneral;
begin
Specific := GetInstance;
{ protected method is callable via public wrapper, static cast again }
TIntroducer(Specific).Useful; // compiles!
end;
I'd like to know:
Also, please comment on remark about TIntroducer
unsafety.
You can use a helper as so :
unit Unit2;
interface
type
TGeneral = class(TObject)
protected procedure Useful; virtual;
end;
TSpecific2 = class(TGeneral)
end;
TSpecificHelper = class helper for TGeneral
public
procedure ExposedUseful;
end;
implementation
procedure TGeneral.Useful;
begin
WriteLn('general');
end;
procedure TSpecificHelper.ExposedUseful;
begin
Useful;
end;
end.
These can even be declared in separate units, for example :
unit Unit2;
interface
type
TGeneral = class(TObject)
protected procedure Useful; virtual;
end;
implementation
procedure TGeneral.Useful;
begin
WriteLn('general');
end;
end.
and separately
unit Unit3;
interface
uses
Unit2;
type
TSpecific2 = class(TGeneral)
end;
TSpecificHelper = class helper for TGeneral
public
procedure ExposedUseful;
end;
implementation
procedure TSpecificHelper.ExposedUseful;
begin
Useful;
end;
end.
And to test :
program Project1;
{$APPTYPE CONSOLE}
uses
//Unit2, // either or
Unit3;
var
foo : TSpecific2;
begin
foo := TSpecific2.Create;
foo.ExposedUseful;
Readln;
end.
Private members can be exposed in a similar way if you make the helper for the base class instead. If in a different unit, a cast is required however. For example :
// in Unit2
TGeneral = class(TObject)
private
procedure AlsoUseful;
protected
procedure Useful; virtual;
end;
//in Unit3
TSpecificHelper = class helper for TGeneral
public
procedure ExposedUseful;
procedure ExposedAlsoUseful;
end;
// ...
implementation
procedure TSpecificHelper.ExposedAlsoUseful;
begin
TGeneral(self).AlsoUseful;
end;
As for polymorphism, you can really just test this out yourself. The helper will apply to whatever descendent class your instance derives from:
TSpecific1 = class(TGeneral)
protected
procedure Useful; override;
end;
// ...
procedure TSpecific1.Useful;
begin
WriteLn('specific 1');
end;
where
TSpecific2 = class(TSpecific1)
end;
Will produce output specific 1
when called with the helper for the base class above.
Note
Starting with Delphi 10.1 Berlin, Class helpers can no longer access strict protected, strict private or private members. This "feature" was actually a compiler bug that Embarcadero has now fixed in Berlin.
Accessing plain protected members with helpers remains possible.