DelphiXEでRTTIを使用してレコードの値を設定しようとしています。GetValueメソッドを使用してレコードから値を取得できますが、SetValueメソッドを使用して値を設定することはできません。
誰かがこれを行う方法を知っていますか/なぜそれが機能しないのですか?
前もって感謝します!
私のコンテキスト:最終的な目標は、任意のXMLファイルを読み取り、アプリケーションのデータモデルにXMLからのデータを自動的に入力するコンポーネントを作成することです。データモデルには、すべての要素のXPathを決定するための注釈が付けられます。オブジェクトと基本的なデータ型については、すでに稼働しています。
TSize = record
X, Y: double;
end;
TMyTest = class
protected
FSize: TSize;
public
constructor Create;
procedure DoStuff;
end;
constructor TMyTest.Create;
begin
FSize.X := 2.7;
FSize.Y := 3.1;
end;
procedure TMyTest.DoStuff;
var
MyContext: TRttiContext;
MyField: TRttiField;
MySizeField: TRttiField;
MyVal: TValue;
MyRecord: TRttiRecordType;
NewVal: TValue;
begin
// Explicit Create of MyContext does not help (as expected)
for MyField in MyContext.GetType(ClassType).GetFields do
if MyField.Name = 'FSize' then //For debugging
begin
MyRecord := MyField.FieldType.AsRecord;
MyVal := MyField.GetValue(Self);
for MySizeField in MyRecord.GetFields do
begin
//This works
NewVal := MySizeField.GetValue(MyVal.GetReferenceToRawData).AsExtended;
NewVal := NewVal.AsExtended + 5.0;
try
// This does not work. (no feedback)
MySizeField.SetValue(MyVal.GetReferenceToRawData, NewVal);
// This however does work. Now to find out what the difference between the two is.
MySizeField.SetValue(@FSize, NewVal);
except
on e: Exception do //Never happens
ShowMessage('Oops!' + sLineBreak + e.Message);
end;
end;
end;
// Shows 'X=2.7 Y=3.1'
// Expected 'X=7.7 Y=8.1'
ShowMessage(Format('X=%f Y=%f', [FSize.X, FSize.Y]));
end;
TSizeがクラスとして宣言されている場合はMyVal.GetReferenceToRawData
、コード内で。に置き換える必要がありますTObject(MyVal.GetReferenceToRawData^)
。これを行うと、すべて期待どおりに機能します。(はい、MyVal.AsObject
その場合もトリックを実行します)これにより、考えられる解決策が導き出されますMyVal.GetReferenceToRawData^
。正しいレコードタイプに型キャストします。しかし、これはどのように行うことができますか?
SetValueで直接@FSizeを使用してみました。ご想像のとおり、これは機能します。これにより、次の質問がトリガーされます。@FSizeとMyVal.GetReferenceToRawDataの違いは何ですか。
さらに調査を重ねた結果、MyValは実際にはレコードのコピーであることがわかりました。実際、Sergが最初の回答で述べたように値は正しく設定されていましたが、コピーで設定されていました。これを早く気づかなかったのはとてもばかげています...
とにかく、以下は機能するコードサンプルです。また、「フィールド名パス」を使用する場合は、この投稿でバリーケリーのアプローチを確認できます。これにより、私は順調に進んでいます。Follow
ルーチンに必要なパスが気に入らなかっただけです。
procedure TMyTest.DoStuff;
var
MyContext: TRttiContext;
MyField: TRttiField;
MySizeField: TRttiField;
NewVal: TValue;
dMyVal: double;
begin
for MyField in MyContext.GetType(ClassType).GetFields do
if MyField.Name = 'FSize' then
begin
for MySizeField in MyField.FieldType.GetFields do
begin
dMyVal := MySizeField.GetValue(PByte(Self) + MyField.Offset).AsExtended;
NewVal := TValue.From(dMyVal + 5.1);
try
MySizeField.SetValue(PByte(Self) + MyField.Offset, NewVal);
except
on e: Exception do
ShowMessage('Oops!' + sLineBreak + e.Message);
end;
end;
end;
if FSize.X > 5.0 then
ShowMessage(Format('X=%f Y=%f', [FSize.X, FSize.Y]));
end;