title: How to set record values using RTTI in Delphi XE
body: <p>I am trying to set the values of a record using RTTI in Delphi XE.
  I can get the value from the record using the GetValue method, but I can not set the value using the SetValue method.</p>
  
  <p>Does anyone know how to do this / why it does not work?</p>
  
  <p>Thanks in advance!</p>
  
  <p>My context:
  Final goal is to write a component which will read any XML file and fill the application's data model with the data from the XML automagically.
  The datamodel will be annotated to detemine the XPaths for all elements.
  For objects and basic data types I already have it up and running.</p>
  
  <pre><code>  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;
  </code></pre>
  
  <p>If TSize is declared as class, then <code>MyVal.GetReferenceToRawData</code> in the code should be replaced by <code>TObject(MyVal.GetReferenceToRawData^)</code>. If you do this, it all works as expected. (Yes, <code>MyVal.AsObject</code> would also do the trick in that case)
  This leads me to a possible solution: Typecast <code>MyVal.GetReferenceToRawData^</code> to the correct record type. How can this be done though?</p>
  
  <p>I just tried using the @FSize directly in SetValue. This works, as you would expect.
  This triggers the question: What is different between @FSize and MyVal.GetReferenceToRawData</p>
  
  <hr>
  
  <p>After lots of further investigations, I found out that MyVal was actually a copy of the record, so indeed the values were set correctly as Serg mentioned in his first answer, however, it was set in a copy. I feel so silly for not realizing this earlier...</p>
  
  <p>Anyway, below is a code sample that does work. Also, if you are willing to use "fieldname paths" you can have a look at Barry Kelly's approach in <a href="https://stackoverflow.com/a/2810732/1353360">this</a> post, which put me on track. I just didn't like the paths needed for the <code>Follow</code> routine.</p>
  
  <pre><code>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 &gt; 5.0 then
      ShowMessage(Format('X=%f Y=%f', [FSize.X, FSize.Y]));
  end;
  </code></pre>