型の実行時操作
RTTI の操作 への移動
型の完全なイントロスペクションが可能なだけでなく、RTTI では、イントロスペクションの対象となる型の変数やインスタンスを操作することもできます。このトピックでは、型情報に基づいて値を実行時に変更できる RTTI オブジェクトで公開されているメソッドおよびプロパティの概要を説明します。
フィールドの操作
フィールドは、情報を格納するクラス メンバまたはレコード メンバです。フィールド情報にアクセスするには、クラス型またはレコード型を記述する TRttiInstanceType インスタンスまたは TRttiRecordType インスタンスをまず取得する必要があります。公開されている GetField メソッド、GetFields メソッド、GetDeclaredFields メソッドのいずれかを使って、個々のフィールドを記述する TRttiField オブジェクトを取得する必要があります。TRttiField では、記述対象となる型のインスタンスにある個々のフィールドの値を設定および取得できる一連のメソッドを公開しています。
以下の例では、クラス型にあるフィールドに関する情報を取得する方法を示しています。フィールドの値は、RTTI を使って間接的に読み取られます。
type
{ フィールドが 2 つあるテスト クラスを定義 }
TTest = class
FInteger: Integer;
FString: String;
end;
var
LContext: TRttiContext;
LClass: TRttiInstanceType;
LVar: TTest;
begin
{ RTTI コンテキストを取得 }
LContext := TRttiContext.Create;
{ TTest クラスの型情報を取得 }
LClass := LContext.GetType(TTest) as TRttiInstanceType;
{ TTest のインスタンスを作成し、その値を初期化 }
LVar := TTest.Create;
LVar.FInteger := 10;
LVar.FString := 'Hello RTTI!';
{ RTTI を使用して LVar の値を出力 }
Writeln('The value of FInteger in LVar is ', LClass.GetField('FInteger')
.GetValue(LVar).ToString());
Writeln('The value of FString in LVar is ', LClass.GetField('FString')
.GetValue(LVar).ToString());
end.
#pragma explicit_rtti fields(public)
class TTest : TObject {
public:
int FInteger;
String FString;
};
int _tmain(int argc, _TCHAR* argv[]) {
// RTTI コンテキストを取得
TRttiContext context;
// TTest クラスの型情報を取得
TRttiType *type = context.GetType(__delphirtti(TTest));
TRttiInstanceType *instanceType = dynamic_cast<TRttiInstanceType*>(type);
if (instanceType) {
// TTest のインスタンスを作成し、その値を初期化
TTest *var = new TTest();
var->FInteger = 10;
var->FString = "Hello RTTI!";
// RTTI を使用して var の値を出力
// フィールド FInteger
TRttiField *field = instanceType->GetField("FInteger");
if (field) {
printf("%ls\n", field->GetValue(var).ToString());
}
// フィールド FString
field = instanceType->GetField("FString");
if (field) {
printf("%ls\n", field->GetValue(var).ToString());
}
}
return 0;
}
フィールドの値を設定するには、以下のように、SetValue メソッドを使用します。
{ LVar インスタンスのフィールドの値を変更 }
LClass.GetField('FInteger').SetValue(100);
LClass.GetField('FString').SetValue('Hello Again!');
GetValue メソッドも SetValue メソッドも、バリアント型の軽量版である TValue という型を扱います。TValue 値を使用するとコードが明確になり、型キャストやメソッドは少なくて済みます。TValue は、扱う値の型キャストを内部で自動的に実行します。
プロパティの操作
ある意味では、プロパティはフィールドとちょうど同じように動作します。つまり、情報値を保持します。プロパティとフィールドの本質的な違いは、プロパティは必ずしも情報値を格納するとは限らず、要求されたときにメソッドを使用して値を生成できることです。プロパティを扱うには、まず TRttiInstanceType オブジェクト(クラス RTTI を表します)を取得し、その後、GetProperty、GetProperties、GetDeclaredProperties のいずれかを使用して、特定のプロパティを操作する必要があります。TRttiProperty でも GetValue メソッドと SetValue メソッドを公開しているため、プロパティの値に簡単にアクセスできます。
以下のコードでは、サンプル クラス TTest が IntProp と StrProp という 2 つの読み書きプロパティを公開すると仮定して、これらのプロパティの値を読み取る方法や変更する方法を示しています。
{ RTTI を使用して、プロパティの値を出力 }
Writeln('The value of IntProp is ', LClass.GetProperty('IntProp').GetValue(LVar).ToString());
Writeln('The value of StrProp is ', LClass.GetProperty('StrProp').GetValue(LVar).ToString());
プロパティの値を設定するには、以下のように、SetValue メソッドを使用します。
{ LVar インスタンスのフィールドの値を変更 }
LClass.GetField('IntProp').SetValue(100);
LClass.GetField('StrProp').SetValue('Hello Again!');
インスタンス プロパティではさらに、クラスに固有の情報(たとえば、Index や Default)も公開します。こうした固有の情報を取得するには、取得された TRttiProperty インスタンスを TRttiInstanceProperty に型キャストします(扱う型は必ずクラスでなければなりません)。
インデックス付きプロパティの操作
XE2(Pulsar)以降では、インデックス付きプロパティが実行時型情報を持つことができます。次のメソッドを使用すると関連する RTTI を取得することができます。
インデックス付きプロパティの値を取得または設定するには、以下のものを渡す必要があります。
- 具体的なクラス インスタンス(インデックス付きでないプロパティの場合と同じ)
- インデックス情報(配列にカプセル化したもの)
以下の例を参照してください。
var
Inst: TStringList;
RttiContext: TRttiContext;
ClassRtti: TRttiType;
IndexedProperty: TRttiIndexedProperty;
begin
// ..
ClassRtti := RttiContext.GetType(TStrings);
Inst := TStringList.Create; // TStrings インスタンスを設定
Inst.Add('CA');
Inst.Add('NV');
// インデックス付けされたプロパティ値を表示(インデックスは指定されなければならない)
IndexedProperty := ClassRtti.GetIndexedProperty('Strings');
WriteLn(IndexedProperty.GetValue(Inst, [0]).AsString); // 'CA' を表示
// ...
// ...
TRttiType *type = context.GetType(__delphirtti(TStrings));
TRttiInstanceType *instanceType = dynamic_cast<TRttiInstanceType*>(type);
TStringList *inst = new TStringList(); // TStrings インスタンスを設定
inst->Add("CA");
inst->Add("NV");
// インデックス付けされたプロパティ値を表示(インデックスは指定されなければならない)
TRttiIndexedProperty *indexedProperty = instanceType->GetIndexedProperty("Strings");
Rtti::TValue index = TValue::From(0); // インデックス 0
System::OpenArray<Rtti::TValue>params(index);
printf("%ls\n", indexedProperty->GetValue(inst, params, params.GetHigh()).AsString().c_str()); // "CA" を表示
// ...
メソッドの呼び出し
RTTI クラスで使用できる最も有用な機能の 1 つはメソッド呼び出しです。メソッド呼び出しでは、静的メソッド、インターフェイス メソッド、クラス メソッド、インスタンス メソッドのいずれかを動的に呼び出すことができます。メソッドを呼び出せるようにするには、呼び出すメソッドを記述する TRttiMethod オブジェクトをまず取得します。TRttiMethod では Invoke というメソッドを公開しているので、それにインスタンス ポインタと一連のパラメータを渡して対象メソッドを動的に呼び出すことができます。
以下のように宣言された TTest クラスがあるとしましょう。
type
TTest = class
public
procedure WriteString(const AStr: String);
end;
procedure TTest.WriteString(const AStr: String);
begin
{ 渡された文字列をそのまま書き出し }
Writeln(AStr);
end;
class __declspec(delphirtti) TTest : public TObject {
public:
void WriteString(UnicodeString AStr) {
// 渡された文字列をそのまま書き出し
printf("%ls", AStr.c_str());
}
};
WriteString メソッドを動的に呼び出すのに使用されるコードは、たとえば以下のようになります。
{ TTest クラスの型情報を取得 }
LClass := LContext.GetType(TTest) as TRttiInstanceType;
{ TTest インスタンスを作成 }
LVar := TTest.Create;
{ WriteString メソッドを呼び出し }
LClass.GetMethod('WriteString').Invoke(LVar, ['Hello World!']);
// TTest クラスの型情報を取得
TRttiInstanceType *LClass =
dynamic_cast<TRttiInstanceType*>(context.GetType(__delphirtti(TTest)));
// TTest インスタンスを作成
std::unique_ptr<TTest>LVar(new TTest()); // RAII ラッパー
// WriteString メソッドを呼び出し
Rtti::TValue msg = TValue::From(UnicodeString("Hello World!"));
System::OpenArray<Rtti::TValue>params(msg);
LClass->GetMethod("WriteString")->Invoke(LVar.get(), params,
params.GetHigh());
WriteString の定義を変更して、インスタンス メソッドではなくクラス メソッドにすると、呼び出しに必要なパラメータも変わります。インスタンスを表すパラメータは、今度は以下のように、インスタンスではなくクラス参照でなければなりません。
{ WriteString クラス メソッドを呼び出し }
LClass.GetMethod('WriteString').Invoke(TTest, ['Hello World!']);
// C++ に同等箇所なし