使用RTTI访问记录的所有元素

5

我想为了调试目的将一个复杂/长记录转储到备忘录中

 TmyRecord =
     aValue : String 
     aNumber : Real;
     Morenumbers   :  Integer ;
     ....
     ....
  end;

我认为Delphi XE 2 RTTI应该让我有机会在循环中获取字段名、字段类型和值,以便将此记录写入备忘录或其他地方。

1
你需要多复杂?只有记录中的简单类型?嵌套记录?变量记录?任意复杂类型?你有RTTI的任何经验吗?没有证据表明你尝试过任何东西。网上有很多例子。你可以使用其中一个JSON库将其转储为JSON,这肯定会带给你更好的示例代码。 - David Heffernan
过程 EnumerateFieldandValues(const AObject: TObject; RecordParams: TStringList); var i: Integer; rtype: TRTTIType; fields: TArray<TRttiField>; begin rtype := TRttiContext.Create.GetType(TypeInfo(aObject.ClassType)); // Memo1.Lines.Add(rtype.ToString); fields := rtype.GetFields; for i := 0 to High(fields) do RecordParams.Add(Format('%s: %s :: %s', [fields[i].Name, fields[i].FieldType.ToString, fields[i].GetValue(@m).ToString])); end; - user1769184
2个回答

20

作为起点 - 记录简单类型。对于复杂的字段(数组、类等),请探索RTTI单元。

type
  TmyRecord = record
    aValue: String;
    aNumber: Real;
    Morenumbers: Integer;
  end;
var
  m: TMyRecord;
  rtype: TRTTIType;
  fields: TArray<TRttiField>;
  i: Integer;
begin
  m.aValue := 'OK';
  m.aNumber := Pi;
  m.Morenumbers := 666;
  rtype := TRTTIContext.Create.GetType(TypeInfo(TMyrecord));
  Memo1.Lines.Add(rtype.ToString);
  fields := rtype.GetFields;
  for i := 0 to High(fields) do
    Memo1.Lines.Add(Format('%s: %s :: %s', [
      fields[i].Name,
      fields[i].FieldType.ToString,
      fields[i].GetValue(@m).ToString]));

输出:

TmyRecord
aValue: string :: OK
aNumber: Real :: 3.14159265358979
Morenumbers: Integer :: 666

1
这是我的尝试。我有类似于你的任务(参见此线程)。它正在进行中,但到目前为止已经做得足够好了。这将枚举TObject内的所有属性,因此您需要适应它以枚举记录:
function EnumerateProperties(const AObject: TObject): String;
var
  rt: TRttiType;
  prop: TRttiProperty;
  value, value2: TValue;
  valstr: String;
  propstr: String;
  fullstr: String;
  bres: Boolean;
  meth: TRttiMethod;
  bytes: TBytes;
  bytes_arr: TArray<TBytes>;
  uints: TArray<UINT32>;
  C1: Integer;
begin
  if not Assigned(AObject) then
    Exit('');

  rt := TRttiContext.Create.GetType(AObject.ClassType);

  fullstr := '';
  // iterate through public properties
  for prop in rt.GetDeclaredProperties do
  begin
    value := prop.GetValue(AObject); // get property value
    valstr := '?';

    // check property type
    case prop.PropertyType.TypeKind of
      tkInteger,
      tkInt64,
      tkFloat: valstr := value.AsVariant;

      tkString,
      tkChar,
      tkWChar,
      tkLString,
      tkWString,
      tkUString: valstr := QuotedStr(value.AsString);

      tkEnumeration: begin
        valstr := 'ENUM';
        if value.TryAsType<Boolean>(bres) then
          valstr := BoolToStr(bres, TRUE)
        else
        begin
          valstr := GetEnumName(value.TypeInfo, prop.GetValue(AObject).AsOrdinal);
        end;
      end;

      tkClass: begin
        // check if property is TList or any of its descendants,
        // then iterate through each of it's members
        meth := prop.PropertyType.GetMethod('ToArray');
        if Assigned(meth) then
        begin
          value2 := meth.Invoke(value, []);
          Assert(value2.IsArray);
          for C1 := 0 to value2.GetArrayLength - 1 do
            valstr := valstr + Format('(%s), ', [EnumerateProperties(value2.GetArrayElement(C1).AsObject)]);
          if valstr <> '' then
            Delete(valstr, Length(valstr) - 1, 2);
          valstr := Format('[%s]', [valstr]);
        end
        else // otherwise, process it as normal class
          valstr := Format('[%s]', [EnumerateProperties(value.AsObject)]);
      end;

      // dynamic arrays
      tkDynArray: begin
        if value.TryAsType<TBytes>(bytes) then // TBytes
          valstr := BytesToHex(bytes)
        else
          if value.TryAsType<TArray<TBytes>>(bytes_arr) then // TArray<TBytes>
          begin
            valstr := '';
            for C1 := Low(bytes_arr) to High(bytes_arr) do
              valstr := valstr + QuotedStr(BytesToHex(bytes_arr[C1])) + ', ';
            if valstr <> '' then
              Delete(valstr, Length(valstr) - 1, 2);
            valstr := Format('(%s)', [valstr]);
          end
          else
            if value.TryAsType<TArray<UINT32>>(uints) then // TArray<UINT32>
            begin
              valstr := '';
              for C1 := Low(uints) to High(uints) do
                valstr := valstr + IntToStr(uints[C1]) + ', ';
              if valstr <> '' then
                Delete(valstr, Length(valstr) - 1, 2);
              valstr := Format('(%s)', [valstr]);
            end;
      end;

      tkUnknown: ;
      tkSet: ;
      tkMethod: ;
      tkVariant: ;
      tkArray: ;
      tkRecord: ;
      tkInterface: ;
      tkClassRef: ;
      tkPointer: ;
      tkProcedure: ;
    end;

    propstr := Format('%s: %s', [prop.Name, valstr]);
    fullstr := fullstr + propstr + '; ';
  end;

  if fullstr <> '' then
    Delete(fullstr, Length(fullstr) - 1, 2);

  result := fullstr;
end;

记录和 RTTI 似乎存在问题-请参见最近的问题:http://stackoverflow.com/questions/23445775/how-to-access-record-properties/23450935#23450935 - MartynA
我的 Delphi 不认识类型 tkInteger ...应该包含哪个单元? - user1769184
tkintger -> 找到 typinfo; - user1769184
@MartynA 那个问题讨论的是属性而不是字段。 - David Heffernan
@David Heffernan,当然可以,但根据省略号判断,OP似乎没有排除属性。 - MartynA

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接