可以按名称更新 TRecord 成员

Possible to update TRecord member by name

当你有记录成员的名称时,是否可以为TMyRecord 获取和设置值?类似于 RTTI.

我不能使用数组,因为成员可能有不同的数据类型。

类型

TMyRecord = record
  X: Integer;
  Y: Integer; 
  Z: DateTime;
end;

var MyRecord: TMyRecord;

procedure UpdateValue(aRecordMemberName: string; AValue: Integer);
begin
  MyRecord[aRecordmemberName] := AValue;
end;

function GetValue(aRecordMemberName: string): Integer;
begin
  Result := MyRecord[aRecordmemberName];
end;

procedure Main();
begin 
  SetValue('X', 5);
  showmessage( GetValue('Y').ToString );
end;

另外请注意,是否可以遍历 Record 的所有成员,类似于遍历 TFieldsTFieldDefs

谢谢。

如果您有固定数量的不同类型的字段,您需要通过字符串名称访问这些字段,这有点奇怪。不过,让我们假设这是正确的做法。

RTTI 有点复杂(意味着你需要写“很多”行代码)而且很慢。当然,在您的情况下它可能足够快,所以它可能足够好。但这并不理想。

根据我的经验,人们往往太急于求助于 RTTI。在大多数情况下,有更好的解决方案。

一个 non-RTTI 解决方案是使用 TDictionary<string, Variant>.

另一个是这样的:

type
  EFrogException = class(Exception);
  TFrogProperty = (fpName, fpBirthDate, fpWeight);
  TFrogPropertyHelper = record helper for TFrogProperty
  strict private
    const PropNames: array[TFrogProperty] of string = ('Name', 'Birth date', 'Weight');
  public
    function ToString: string;
    class function FromString(const APropName: string): TFrogProperty; static;
  end;

  TFrog = record
  strict private
    FProperties: array[TFrogProperty] of Variant;
  private
    function GetProp(Prop: TFrogProperty): Variant;
    procedure SetProp(Prop: TFrogProperty; const Value: Variant);
    function GetPropByName(APropName: string): Variant;
    procedure SetPropByName(APropName: string; const Value: Variant);
  public
    property Prop[Prop: TFrogProperty]: Variant read GetProp write SetProp;
    property PropByName[Prop: string]: Variant read GetPropByName write SetPropByName; default;
  end;

哪里

{ TFrogPropertyHelper }

class function TFrogPropertyHelper.FromString(
  const APropName: string): TFrogProperty;
begin
  for var Prop := Low(TFrogProperty) to High(TFrogProperty) do
    if SameText(Prop.ToString, APropName) then
      Exit(Prop);
  raise EFrogException.CreateFmt('Invalid frog property: "%s".', [APropName]);
end;

function TFrogPropertyHelper.ToString: string;
begin
  if InRange(Ord(Self), Ord(Low(TFrogProperty)), Ord(High(TFrogProperty))) then
    Result := PropNames[Self]
  else
    Result := '';
end;

{ TFrog }

function TFrog.GetProp(Prop: TFrogProperty): Variant;
begin
  Result := FProperties[Prop];
end;

function TFrog.GetPropByName(APropName: string): Variant;
begin
  Result := Prop[TFrogProperty.FromString(APropName)];
end;

procedure TFrog.SetProp(Prop: TFrogProperty; const Value: Variant);
begin
  FProperties[Prop] := Value;
end;

procedure TFrog.SetPropByName(APropName: string; const Value: Variant);
begin
  Prop[TFrogProperty.FromString(APropName)] := Value;
end;

那么你可以这样做:

procedure TForm1.FormCreate(Sender: TObject);
begin

  var James: TFrog;

  James['Name'] := 'James';
  James['Birth date'] := EncodeDate(2016, 05, 10);
  James['Weight'] := 2.4;

  ShowMessage(James['Name']);

  James['Name'] := 'Sir James';
  ShowMessage(James['Name']);

  // And you can still be type safe if you want to:

  James.Prop[fpName] := 'Sir James Doe';
  ShowMessage(James.Prop[fpName]);

end;