Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi: writing to the private ancestor's field in descendant class

I need to fix a third-party component. This component's class has private variable which is actively used by its descendants:

TThirdPartyComponentBase = class
private
  FSomeVar: Integer;
public
  ...
end;

TThirdPartyComponent = class (TThirdPartyComponentBase)
protected
   procedure Foo; virtual;
end;

procedure TThirdPartyComponent.Foo;
begin
  FSomeVar := 1; // ACCESSING PRIVATE FIELD!
end; 

This works because both classes are in the same unit, so they're kinda "friends".

But if I'll try to create a new class in a new unit

TMyFixedComponent = class (TThirdPartyComponent)
  procedure Foo; override; 
end;

I can't access FSomeVar anymore, but I need to use it for my fix. And I really don't want to reproduce in my code all that tree of base classes.

Can you advise some quick hack to access that private field without changing the original component's unit if it's possible at all?

like image 825
Andrew Avatar asked Oct 28 '10 13:10

Andrew


2 Answers

By the use of class helpers it's possible to accomplish access to the private parts of the base class from the derived class without loosing type safety.

Just add these declarations in another unit:

Uses YourThirdPartyComponent;

type
  // A helper to the base class to expose FSomeVar
  TMyBaseHelper = class helper for TThirdPartyComponentBase
  private
    procedure SetSomeVar( value : integer);
    function GetSomeVar: integer;
  public
    property SomeVar:integer read GetSomeVar write SetSomeVar;
  end;

  TMyFixedComponent = class helper for TThirdPartyComponent
  protected
    procedure Foo;
  end;

procedure TMyFixedComponent.Foo;
begin
  // Cast to base class and by the class helper TMyBaseHelper the access is resolved
  TThirdPartyComponentBase(Self).SomeVar := 1; 
end;

function TMyBaseHelper.GetSomeVar: integer;
begin
  Result := Self.FSomeVar; // ACCESSING PRIVATE FIELD!
end;

procedure TMyBaseHelper.SetSomeVar(value: integer);
begin
  Self.FSomeVar := value; // ACCESSING PRIVATE FIELD!
end;

// Testing
var
  TSV: TThirdPartyComponent;
begin
  TSV := TThirdPartyComponent.Create;
  try
    TSV.Foo;    
    WriteLn(IntToStr(TSV.SomeVar));  // Writes 1
  finally
    TSV.Free;
  end;
end.

As can be seen from comments in code, FSomeVar is exposed by a class helper from the TThirdPartyComponentBase class. Another class helper for the TThirdPartyComponent implements the Foo procedure. In there, access to the SomeVar property of the base class helper is made via a type cast to the base class.

like image 91
LU RD Avatar answered Sep 28 '22 12:09

LU RD


You have to use a hack to access a private field in any class (including a base class) in a different unit. In your case define in your unit:

type
  __TThirdPartyComponentBase = class 
  private 
    FSomeVar: Integer;
  end;

Then get the access:

__TThirdPartyComponentBase(Self).FSomeVar := 123;

Of course, that is dangerous, because you will need to control changes in the base class. Because if the fields layout will be changed and you will miss this fact, then the above approach will lead to failures, AV's, etc.

like image 45
oodesigner Avatar answered Sep 28 '22 12:09

oodesigner