views:

284

answers:

2

Has anyone an idea, how I can make TValue using a reference to the original data? In my serialization project, I use (as suggested in XML-Serialization) a generic serializer which stores TValues in an internal tree-structure (similar to the MemberMap in the example).

This member-tree should also be used to create a dynamic setup form and manipulate the data. My idea was to define a property for the Data:

TDataModel <T> = class
  {...}
  private
    FData : TValue;
    function GetData : T;
    procedure SetData (Value : T);
  public
    property Data : T read GetData write SetData;
end;

The implementation of the GetData, SetData Methods:

procedure TDataModel <T>.SetData (Value : T);

begin
FData := TValue.From <T> (Value);
end;

procedure TDataModel <T>.GetData : T;

begin
Result := FData.AsType <T>;
end;

Unfortunately, the TValue.From method always makes a copy of the original data. So whenever the application makes changes to the data, the DataModel is not updated and vice versa if I change my DataModel in a dynamic form, the original data is not affected. Sure I could always use the Data property before and after changing anything, but as I use lot of Rtti inside my DataModel, I do not realy want to do this anytime.

Perhaps someone has a better suggestion?

+4  A: 

A TValue is designed to hold any kind of data in a very compact form, it's not designed to emulate an "pointer". Take a look in the RTTI.pas unit: TValue is a RECORD that only has one data member of the type TValueData. TValueData itself is itself a variant record.

Looking at TValueData you will see how it does NOT hold anything but the minimum amount of data: There's no way to know where that TValue came from.

The solution: Don't hold a TValue in your structures, replace it with a pair of TRttiField + an TObject. When you need the TValue use TRttiField.GetValue(Instance), when you want to set a value use TRttiField.SetValue(Instance, AValue:TValue).

Cosmin Prund
This was the first approach, and you are right: This will work for Objects. But what if T is a record and not a TObject?
Christian Metzler
If it's a record I assume you'd have an plain Pointer. The TRttiField.GetValue and TRttiField.SetValue take an Instance parameter of the Pointer type, not TObject... it's just that I never used the RTTI for records, never had the need. The RTTI in Delphi 2010 is geared to nicely handle both records and classes! Let me put this an other way: In place of TRttiField use whatever object you got from RTTI in the first place (TRttiProperty?) and in place of TObject use whatever your TRtti object requires. You already have everything you need, since you found a way to get a TValue in the first place.
Cosmin Prund
A: 

Thanks Cosmin for your help, the solution is not to save a TValue in the structure but use a Pointer to the data and use the GetValue, SetValue methods of a field or property.

So here is how I solved it in my generic class:

TDataModel <T> = class
  private
    FType     : PTypeInfo;
    FInstance : Pointer;
  public 
    constructor Create (var Data : T);
    procedure ShowContent;
  end;

constructor TDataModel <T>.Create (var Data : T);

begin
FType := TypeInfo(T);
if FType.Kind = tkClass then
  FInstance := TObject (Data)
else if FType.Kind = tkRecord then
  FInstance := @Data;
end;

procedure TDataModel <T>.ShowContent;
var 
  Ctx   : TRttiContext;
  Field : TRttiField;

begin
for Field in Ctx.GetType (FType).GetFields do
  Writeln (Field.GetValue (FInstance).ToString);
end;

Now you can either change fields using the DataModel through or using the original record class.

Christian Metzler