delphioopclass-helpers

How do I call protected methods using class helper?


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.


Solution

  • 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.