Home > Back-end >  Possible to update TRecord member by name
Possible to update TRecord member by name

Time:02-25

Is it possible to have a get and set value for TMyRecord when you have the name of the record member? something similar to RTTI.

I cannot use an array as the members may have different data types.

type

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;

On an additional note, is it possible to iterate through all members of a Record, similar to iterating through TFields or TFieldDefs?

thanks.

  • Using Delphi 11 in Firemonkey

CodePudding user response:

If you have a fixed number of fields of different types, it is somewhat strange that you need to access these by string names. Still, let's assume this is the right thing to do.

RTTI is a bit complicated (meaning that you need to write "many" lines of code) and rather slow. Sure, it will probably be fast enough in your case, so it will probably be good enough. But it isn't ideal.

In my experience, people are often too eager to resort to RTTI. In most cases, there are better solutions.

One non-RTTI solution would be to use a TDictionary<string, Variant>.

Another would be like this:

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;

where

{ 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;

Then you can do things like this:

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;
  • Related