Home > Blockchain >  How to access class variables in a TList of Metaclass references?
How to access class variables in a TList of Metaclass references?

Time:10-16

I have a class like this:

type
  TCipher = class
    private

    public
        function Encrypt(sText: String; Key: TObject): String; virtual; abstract;
        function Decrypt(sText: String; Key: TObject): String; virtual; abstract;
        class var
          sName: String;
          sDisplayName: String;
  end;
  TCipherClass = class of TCipher;
  
  TCaesar = class(TCipher)
    private

    public
        function Encrypt(sText: String; Key: TObject): String; override;
        function Decrypt(sText: String; Key: TObject): String; override;
        class var
          sName: String;
          sDisplayName: String;
  end;

implementation

function TCaesar.Encrypt(sText: String; Key: TObject): String;
begin
  
end;

function TCaesar.Decrypt(sText: String; Key: TObject): String;
begin
  
end;

initialization

  TCipher.sName := 'defaultName';
  TCipher.sDisplayName := 'defaultDipsplay';
  ShowMessage(TCipher.sName   '  '   TCipher.sDisplayName);
  
  TCaesar.sName := 'caesar';
  TCaesar.sDisplayName := 'Caesar Cipher';
  ShowMessage(TCaesar.sName   '  '   TCaesar.sDisplayName);

end.

Elsewhere, I have a TList of the classes like this:

var
  lstCiphers: TList<TCipherClass>;
  cipher: TCipherClass;
begin
  lstCiphers := TList<TCipherClass>.Create;
  lstCiphers.AddRange([TCaesar]);

  for cipher in lstCiphers do
    ShowMessage('In list: '   cipher.ClassName   ' . '   cipher.sDisplayName);
end;

But this returns defDipsplay instead of Caesar Cipher. I think this is something I'm doing wrong with the list, because when I show it straight from the class it shows fine:

ShowMessage(TCipher.ClassName   ' . '   TCipher.sDisplayName);
ShowMessage(TCaesar.ClassName   ' . '   TCaesar.sDisplayName);

CodePudding user response:

Your variables are declared as class var, so they are specific to each class that declares them. You have class variables declared in TCaesar that have the same names as class variables in TCipher, but when you try to access those variables at runtime via a TCipherClass metaclass reference, the compiler doesn't know which actual class the metaclass refers to, so all it can do at compile-time is generate code to access the TCipher-specific variables, not the TCaesar-specific variables.

In this situation, I would suggest using class virtual methods instead of class vars, then each class can override the methods to return different values at runtime.

type
  TCipher = class
  public
    function Encrypt(sText: String; Key: TObject): String; virtual; abstract;
    function Decrypt(sText: String; Key: TObject): String; virtual; abstract;
    class function CipherName: String; virtual;
    class function DisplayName: String; virtual;
  end;
  TCipherClass = class of TCipher;
  
  TCaesar = class(TCipher)
  public
    function Encrypt(sText: String; Key: TObject): String; override;
    function Decrypt(sText: String; Key: TObject): String; override;
    class function CipherName: String; override;
    class function DisplayName: String; override;
  end;

implementation

function TCaesar.Encrypt(sText: String; Key: TObject): String;
begin
  ...  
end;

function TCaesar.Decrypt(sText: String; Key: TObject): String;
begin
  ...  
end;

class function TCipher.CipherName: String; 
begin
  Result := 'defaultName';
end;

class function TCipher.DisplayName: String;
begin
  Result := 'defaultDipsplay';
end;

class function TCaesar.CipherName: String;
begin
  Result := 'caesar';
end;

class function TCaesar.DisplayName: String;
begin
  Result := 'Caesar Cipher';
end;

initialization
  ShowMessage(TCipher.CipherName   '  '   TCipher.DisplayName);
  ShowMessage(TCaesar.CipherName   '  '   TCaesar.DisplayName);

end.
var
  lstCiphers: TList<TCipherClass>;
  cipher: TCipherClass;
begin
  lstCiphers := TList<TCipherClass>.Create;
  lstCiphers.Add(TCaesar);

  for cipher in lstCiphers do
    ShowMessage('In list: '   cipher.ClassName   ' . '   cipher.CipherName   '.'   cipher.DisplayName);
end;

CodePudding user response:

You've redeclared sName and sDisplayName in TCaesar so these are hiding the same-named variables in the ancestor TCipher class. Just remove that declaration and you get the behaviour you expect.

  TCaesar = class(TCipher)
    private

    public
        function Encrypt(sText: String; Key: TObject): String; override;
        function Decrypt(sText: String; Key: TObject): String; override;            
  end;

The sName and sDisplayName fields will still be members of TCaesar here because they will be inherited from TCipher. If you declare new variables with the same name in a descendent class then they will hide the ancestor variables when referenced directly via a reference of the descendent type. When using a variable that treats the object as an ancestor, however, you will read and write to the ancestor's fields instead.

  • Related