属性の実行時抽出

提供: RAD Studio
移動先: 案内検索

属性(RTTI) への移動

属性の実行時の側面(属性の抽出方法、属性の情報値に応じて判断を下す方法)について説明します。

属性のインスタンス化

注釈は(「型および型メンバの注釈付け」で説明したとおり)、型やメンバに属性を付加する簡単な方法です。 コンパイル済みバイナリに組み込まれる情報は、属性のクラス、選択されたコンストラクタへのポインタ、インスタンス化時に属性のコンストラクタに渡される定数のリストのみです。

属性の実際のインスタンス化は、指定された型または型メンバに含まれているそれらの属性をコンシューマ(ユーザー)コードで問い合わせたときに起こります。 つまり、属性クラスのインスタンスは自動的に生成されるわけではなく、プログラムで属性を明示的に検索したときに生成されるのです。 属性のインスタンス化に決まった順序はなく、生成されるインスタンスの数もわかりません。 そうした結果に依存するプログラムは書かないでください。

次のような属性宣言について考えてみましょう。

type
  TSpecialAttribute = class(TCustomAttribute)
  public
    FValue: String;

    constructor Create(const AValue: String);
  end;

constructor TSpecialAttribute.Create(const AValue: String);
begin
  FValue := AValue;
end;

この TSpecialAttribute は以下の例で注釈として使用されます。

type
  [TSpecialAttribute('Hello World!')]
  TSomeType = record
  ...
  end;

ユーザー コードで TSomeType 型から属性を抽出するには、System.Rtti ユニットから提供される機能を利用する必要があります。 以下では抽出コードの例を示します。

var
  LContext: TRttiContext;
  LType: TRttiType;
  LAttr: TCustomAttribute;
begin
  { 新しい Rtti コンテキストを作成 }
  LContext := TRttiContext.Create
  
  { TSomeType 型の型情報を抽出 }
  LType := LContext.GetType(TypeInfo(TSomeType));

  { カスタム属性を検索し、何らかのカスタム処理を行う }
  for LAttr in LType.GetAttributes() do
    if LAttr is TSpecialAttribute then
      Writeln(TSpecialAttribute(LAttr).FValue);
 
  { コンテキストを破壊 }
  LContext.Free;
end;

上記の例でわかるとおり、ユーザーは、型に注釈として付加される属性を問い合わせるコードを明示的に記述する必要があります。 実際の属性インスタンスは、TRttiType.GetAttributes メソッドで作成されます。 上記の例ではインスタンスを破棄していないことに注意してください。すべてのリソースは後で TRttiContext で解放されます。

例外

属性の実際のインスタンス化はユーザー コードで行われるため、属性のコンストラクタで発生する可能性のある例外に留意する必要があります。 属性を問い合わせるコードを try .. except 節で囲むことを一般に推奨します。

この問題の例を示すため、元の例に含まれていた属性コンストラクタをたとえば以下のように変更します。

constructor TSpecialAttribute.Create(const AValue: String);
begin
  if AValue = '' then
    raise EArgumentException.Create('Expected a non-null string');

  FValue := AValue;
end;

また、以下のように、属性のコンストラクタに空の文字列を渡すように TSomeType の注釈を変更します。

type
  [TSpecialAttribute('')]
  TSomeType = record
  ...
  end;

この場合、TSomeType 型の属性を問い合わせるコードは失敗し、EArgumentException 例外が発生します。この例外は、インスタンス化中の属性から送出されます。 このような場合には、次のように、try .. except 節で囲むように問い合わせコードを変更することをお勧めします。

  { カスタム属性を検索し、何らかのカスタム処理を行う }
  try
    for LAttr in LType.GetAttributes() do
      if LAttr is TSpecialAttribute then
        Writeln(TSpecialAttribute(LAttr).FValue);
  except
    { ... ここで何らかの処理を行う ... }
  end;

関連項目