8

古いプロジェクトを維持するために Delphi 2007 を使用しています。クラス参照変数からクラス定数にアクセスする際に問題があり、常に子クラス定数ではなく親クラス定数を取得します。

親クラス、いくつかの子クラス、クラス参照、そして最後に、ループの目的でクラス参照を格納するための const 配列があるとします。

次の簡単なプログラムを見てください。

program TestClassConst;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

  TParent = class
  const
    ClassConst = 'BASE CLASS';
  end;

  TChild1 = class(TParent)
  const
    ClassConst = 'CHILD 1';
  end;

  TChild2 = class(TParent)
  const
    ClassConst = 'CHILD 2';
  end;

  TParentClass = class of TParent;
  TChildClasses = array[0..1] of TParentClass;

const
  ChildClasses: TChildClasses = (TChild1, TChild2);

var
  i: integer;
  c: TParentClass;
  s: string;

begin
  try
    writeln;

    writeln('looping through class reference array');
    for i := low(ChildClasses) to high(ChildClasses) do begin
      c := ChildClasses[i];
      writeln(c.ClassName, ' -> ', c.ClassConst);
    end;

    writeln;

    writeln('accessing classes directly');
    writeln(TChild1.ClassName, ' -> ', TChild1.ClassConst);
    writeln(TChild2.ClassName, ' -> ', TChild2.ClassConst);

  except
    on E: Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

実行すると、次のようになります。

looping through class reference array
TChild1 -> BASE CLASS
TChild2 -> BASE CLASS

accessing classes directly
TChild1 -> CHILD 1
TChild2 -> CHILD 2

'CHILD 1' と 'CHILD 2' も配列ループにあるはずです!

クラス参照で機能しない理由を誰かに説明できますか?

4

1 に答える 1

8

型指定されていないクラス定数は、スコープが追加された通常の定数です。
型付きクラス定数は、実際には変更できないクラス変数です。
問題は、クラス変数が仮想ではないことです。

Hallvard Vassbotn がこの問題について次の場所に書いています:パート 1パート 2

言語は仮想クラス変数をサポートしていないため、クラス参照からクラス変数とクラス定数にアクセスすることはできません。コンパイラがこれを翻訳してから、残りのコンパイルに 進む
と言った場合。s:= TClass1.SomeConsts:= SomeGlobalButHiddenConst

class varそしてclass const、構文糖にすぎません。
このように、 と実際のクラスの間のリンクはclass var/constコンパイル時にのみ存在し、Java の型消去のように実行時に壊れます。

RTTI も役に立たない: RTTI を使用してクラスから定数フィールドを取得
する D2007 を使用している場合、唯一のオプションは、必要な定数を返す仮想関数を宣言することだと思います。

D2010 より前のオプション: 仮想方式

TParent = class
  class function Name: string; virtual;
end;

TChild1 = class(TParent)
  class function name: string; override;
....
class function TParent.name: string;
begin
  Result:= Self.ClassConst;
end;

class function TChild1.name: string;
begin
  Result:= Self.ClassConst;   //Silly copy paste solution
end;

これは悲しい状況ですが、他に選択肢はありません。

Delphi 2010 以降:属性を使用する より良いオプションは、属性を使用することです。属性には、 RTTI
を使用してアクセスできます。

次のコードが機能します。

program TestClassConst;

{$APPTYPE CONSOLE}

uses
  SysUtils, rtti;

type

  NameAttribute = class(TCustomAttribute)
  private
    Fname: string;
  public
    constructor Create(const Name: string);
    property Name: string read Fname;
  end;

  [Name('Base class')]
  TParent = class
  const
    ClassConst = 'BASE CLASS';
  private
  public
    class function Name: string;
  end;

  [Name('Child 1')]
  TChild1 = class(TParent)
  const
    ClassConst = 'CHILD 1';
  end;

  [Name('Child 2')]
  TChild2 = class(TParent)
  const
    ClassConst = 'CHILD 2';
  end;

  TParentClass = class of TParent;
  TChildClasses = array[0..1] of TParentClass;

const
  ChildClasses: TChildClasses = (TChild1, TChild2);

var
  i: integer;
  c: TParentClass;
  s: string;

{ TParent }

class function TParent.Name: string;
var
  Context: TRttiContext;
  ClassData: TRttiType;
  Attr: TCustomAttribute;
begin
  Context:= TRttiContext.Create;
  ClassData:= Context.GetType(Self);
  try
    for Attr in ClassData.GetAttributes do begin
      if Attr is NameAttribute then Result:= NameAttribute(Attr).Name;
    end;
  finally
    ClassData.Free;
  end;
end;

{ NameAttribute }

constructor NameAttribute.Create(const Name: string);
begin
  inherited Create;
  FName:= name;
end;

begin
  writeln;

  writeln('looping through class reference array');
  for i := low(ChildClasses) to high(ChildClasses) do begin
    c := ChildClasses[i];
    writeln(c.ClassName, ' -> ', c.Name);
  end;

  writeln;

  writeln('accessing classes directly');
  writeln(TChild1.ClassName, ' -> ', TChild1.Name);
  writeln(TChild2.ClassName, ' -> ', TChild2.Name);
  readln;
end.
于 2016-06-10T14:06:25.250 に答える