2010-10-28 18 views
10

J'ai besoin de réparer un composant tiers. La classe de cette composante a variable privée qui est activement utilisé par ses descendants:Delphi: écrire dans le champ de l'ancêtre privé dans la classe descendante

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; 

Cela fonctionne parce que les deux classes sont dans la même unité, ils sont donc Kinda « amis ».

Mais si je vais essayer de créer une nouvelle classe dans une nouvelle unité

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

Je ne peux pas accéder à FSomeVar plus, mais je dois l'utiliser pour mon correctif. Et je ne veux vraiment pas reproduire dans mon code tout cet arbre des classes de base.

Pouvez-vous conseiller un piratage rapide pour accéder à ce champ privé sans changer l'unité du composant d'origine si c'est possible du tout?

Répondre

5

Vous devez utiliser un hack pour accéder à un champ privé dans n'importe quelle classe (y compris une classe de base) dans une unité différente. Dans votre cas, définir dans votre unité:

type 
    __TThirdPartyComponentBase = class 
    private 
    FSomeVar: Integer; 
    end; 

ensuite obtenir l'accès:

__TThirdPartyComponentBase(Self).FSomeVar := 123; 

Bien sûr, cela est dangereux, parce que vous devrez contrôler les changements dans la classe de base. Parce que si la mise en page des champs sera modifiée et vous manquerez ce fait, alors l'approche ci-dessus mènera à des échecs, des AV, etc.

+2

@Andrew: notez que cette solution se casse dès que la disposition de la mémoire du composant ancêtre (tiers) change. Vous pourriez ne pas remarquer que ça casse, car rien ne vous en avertira. Ou vous pourriez voir de faux comportements erronés (si vous êtes chanceux: violations d'accès) parce que vous commencez à écraser des données qui ne vous appartiennent pas. –

+0

@Jeroen Pluimers J'ai remarqué Andrew à ce sujet. Mais il n'y a pas d'autres solutions pour ce problème. – oodesigner

+0

Les assistants de classe peuvent le faire sans piratage, voir ma réponse :) –

-1

Expose la valeur de la variable privée par une propriété protégée dans 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; 

Dans TMyFixedComponent classe utiliser la propriété MyVar dans la procédure que vous souhaitez remplacer.

+5

Mais cela va changer le code original de TThirdPartyComponent. Je voulais une solution sans réécrire le code de TThirdPartyComponent ou changer l'unité du composant d'origine. – Andrew

0

Je ne sais pas si cela aidera, mais je me souviens qu'il y a un moyen pour "casser" une variable privée en visibilité. Je sais, par exemple, que j'ai rencontré des avertissements du compilateur lorsque j'ai déplacé une propriété de moins bonne visibilité (dans la classe de base) à un niveau plus visible (dans mon descendant). L'avertissement a déclaré qu'il est déclaré à un autre niveau de visibilité ...

Cela fait un certain temps et je ne suis pas certain, mais je crois que ce que vous pouvez faire, c'est que votre descendant déclare la même variable que protégée. (Vous devrez peut-être utiliser le mot-clé Redeclare pour compiler.)

Désolé, je n'ai pas d'informations plus spécifiques sur la façon de faire cela (si c'est possible.) Peut-être que cette publication demandera un des sorciers ici en me corrigeant! :-)

18

En utilisant class helpers, il est possible d'accéder aux parties privées de la classe de base à partir de la classe dérivée sans perdre le type de sécurité.

il suffit d'ajouter ces déclarations dans une autre 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. 

Comme on peut le voir d'après les commentaires dans le code, FSomeVar est exposé par un assistant de classe de la classe TThirdPartyComponentBase. Un autre assistant de classe pour le TThirdPartyComponent implémente la procédure Foo. Dans là, l'accès à la propriété SomeVar de l'assistant de classe de base est effectué via un cast de type à la classe de base.

+1

Très bien! J'aime ça. –

+3

Notez que les aides de classe d'une autre unité ne peuvent plus accéder aux membres privés à partir de Delphi 10.1 Berlin, car cela a été considéré comme un bug par Embarcadero. Voir http://stackoverflow.com/questions/9410485/how-do-i-use-class-helpers-to-access-strict-mrivers-private-of-a-class#comment61026976_9410717 pour plus de détails –