Delphi: запись в поле частного предка в классе потомков

Мне нужно исправить сторонний компонент. Этот класс компонента имеет частную переменную, которая активно используется ее потомками:

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; 

Это работает, потому что оба класса находятся в одной единице, поэтому они являются "друзьями".

Но если я попытаюсь создать новый класс в новом модуле

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

Я больше не могу получить доступ к FSomeVar, но мне нужно использовать его для моего исправления. И я действительно не хочу воспроизводить в своем коде все это дерево базовых классов.

Можете ли вы посоветовать быстрый хак для доступа к этому частному полю без изменения исходного компонента, если это вообще возможно?

Ответ 1

Вы должны использовать хак для доступа к частному полю в любом классе (включая базовый класс) в другом блоке. В вашем случае укажите в своем устройстве:

type
  __TThirdPartyComponentBase = class 
  private 
    FSomeVar: Integer;
  end;

Затем получите доступ:

__TThirdPartyComponentBase(Self).FSomeVar := 123;

Конечно, это опасно, потому что вам нужно будет контролировать изменения в базовом классе. Потому что, если макет полей будет изменен, и вы пропустите этот факт, тогда вышеупомянутый подход приведет к сбоям, AV и т.д.

Ответ 2

Используя class helpers, можно получить доступ к частным частям базового класса из производного класса без потери безопасности типов.

Просто добавьте эти объявления в другое подразделение:

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.

Как видно из комментариев в коде, FSomeVar открывается хелпера класса из класса TThirdPartyComponentBase. Другой помощник класса для TThirdPartyComponent реализует процедуру Foo. В этом случае доступ к свойству SomeVar вспомогательного класса базового класса выполняется с помощью типа cast в базовый класс.

Ответ 3

Не знаю, поможет ли это, но я, похоже, помню, что есть способ "взломать" личную переменную в видимость.

Я знаю, например, что я встретил предупреждения от компилятора, когда я переместил свойство из более низкой видимости (в базовом классе) на более видимый уровень (у моего потомка). Предупреждение заявило, что оно объявляется на другом уровне видимости...

Это было какое-то время, и я не уверен, но я верю, что вы можете сделать это в своем потомке, объявив ту же переменную, что и защита. (Возможно, вам придется использовать ключевое слово Redeclare для компиляции.)

Извините, у меня нет более конкретной информации о том, как это сделать (если это действительно возможно.) Возможно, это сообщение подскажет одному из мастеров здесь исправить меня!:-)

Ответ 4

Вывести значение частной переменной защищенным свойством в TThirdPartyComponent.

TThirdPartyComponent = class (TThirdPartyComponentBase)
private
   Procedure SetValue(Value: Integer);
   Function GetValue: Integer;
protected
   Property MyVar: Integer read GetValue write Setvalue; 
   procedure Foo; virtual;
end;

Procedure TThirdPartyComponent.SetValue(Value: Integer);
begin
  FSomeVar := Value ;
end;

Function GetValue: Integer;
begin
  result := FSomeVar;
end;

В классе TMyFixedComponent используйте свойство MyVar в процедуре, которую вы хотите переопределить.