我正在尝试在 Delphi XE 中使用 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 在他的第一个答案中提到的那样正确设置了值,但是,它是在副本中设置的。我觉得很傻,没有早点意识到这一点……
无论如何,下面是一个有效的代码示例。此外,如果您愿意使用“字段名称路径”,您可以在这篇文章中查看 Barry Kelly 的方法,这让我走上了正轨。我只是不喜欢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;