如何访问 Metaclass 引用的 TList 中的 class 变量?

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

我有一个 class 这样的:

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.

在其他地方,我有 TList 个 class 是这样的:

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;

但是这个returns defDipsplay 而不是Caesar Cipher。我认为这是我在列表中做错的地方,因为当我直接从 class 显示它时它显示正常:

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

您在 TCaesar 中重新声明了 sNamesDisplayName,因此它们在祖先 TCipher class 中隐藏了同名变量。只需删除该声明,您就会得到预期的行为。

  TCaesar = class(TCipher)
    private

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

sNamesDisplayName 字段在这里仍将是 TCaesar 的成员,因为它们将从 TCipher 继承。如果您在后代 class 中声明具有相同名称的新变量,那么它们将在通过后代类型的引用直接引用时隐藏祖先变量。但是,当使用将对象视为祖先的变量时,您将改为读取和写入祖先的字段。

您的变量声明为 class var,因此它们特定于声明它们的每个 class。您在 TCaesar 中声明了 class 个变量,它们与 TCipher 中的 class 个变量同名,但是当您尝试在运行时通过 TCipherClass metaclass 引用,编译器不知道 metaclass 指的是哪个实际的 class,所以它在编译时所能做的就是生成代码来访问 TCipher-特定变量,而不是 TCaesar-特定变量。

在这种情况下,我会建议使用class virtual方法而不是class vars,那么每个class可以override方法与return不同运行时的值。

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;