How do I call protected methods using a class helper?

Suppose we have classes with a method that is potentially very useful, but is not available due to the 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 of two old school methods that can be used for such a method, both include inheritance and type. Both approaches should work the same with base case # 1 and extended 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` 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:

  • how to achieve the same result using the power of class helpers?
  • Can a private method be called with a class helper?
  • would there be any difference between case # 1 and case # 2 because the helper class increases the scope of the class rather than the internal view?
  • how to call the original method from one re-introduced class in the helper without the risk of recursion?

Also comment on the TIntroducer

insecurity note .

+3


source to share


1 answer


You can use the helper like this:

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.

      

They 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 check:

program Project1;


{$APPTYPE CONSOLE}

uses
  //Unit2,  // either or
  Unit3;

var
  foo : TSpecific2;
begin
  foo := TSpecific2.Create;
  foo.ExposedUseful;
  Readln;
end.

      



Private members can be displayed in a similar way if you create a helper for the base class instead. However, if a roll is required in another block. 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 far as polymorphism is concerned, you can just test it yourself. The helper will apply to any descendant class from which your instance has the following result:

  TSpecific1 = class(TGeneral)
    protected
      procedure Useful; override;
  end;

// ...

procedure TSpecific1.Useful;
begin
  WriteLn('specific 1');
end;

      

Where

TSpecific2 = class(TSpecific1)
end;

      

The output will be issued specific 1

when called with the helper for the base class above.

Note
As of Delphi 10.1 Berlin, Class helpers can no longer access strict protected, strict private or private members. This "feature" was actually a compiler error that Embarcadero has now fixed in Berlin.
It remains possible to access simple protected members using helpers.

+5


source







All Articles