Delphi properties with one receiver and setter

I am trying to implement a wrapper around a config file class and it will be easier to use one function to get and one function to set values ​​in properties.

The code below is the minimal version of what I am trying to achieve.

Any help would be greatly appreciated.

unit Config;

interface

uses Rtti;

type
  Group = class(TCustomAttribute)
  strict private
    FName: string;

  public
    constructor Create(const Name: string);

    property Name: string read FName;
  end;

  IConfig = class
  protected
    function GetString: string;
    procedure SetString(const Value: string);
  end;

  TConfig = class(IConfig)
  public
    [Group('Person')]
    property Name: string read GetString write SetString;
    [Group('Person')]
    property City: string read GetString write SetString;
  end;

implementation

{ Group }

constructor Group.Create(const Name: string);
begin
  FName := Name;
end;

{ IConfig }

function IConfig.GetString: string;
begin
  // Here I would need the following from the property that call this function:
  // * Property name
  // * Property attribute name

  // This kind of code will not work, because it loop through all available properties
  (*
    var
      ctx: TRttiContext;
      objType: TRttiType;
      Prop: TRttiProperty;
    begin
      ctx := TRttiContext.Create;
      objType := ctx.GetType(Obj.ClassInfo);
      for Prop in objType.GetProperties do begin
        if Prop.GetClassType is TClassBase then
          // do something special with base class properties
        else
          // standard functionality on all other properties
      end;
    end;
  *)
end;

procedure IConfig.SetString(const Value: string);
begin
  // Need the same as above
end;

end.

      

+3


source to share


2 answers


Property getters and setters don't know which property is calling them. The only way for a generic getter / setter to know is to use a specifier index

, e.g .:

unit Config;

interface

uses Rtti;

type
  Group = class(TCustomAttribute)
  strict private
    FName: string;

  public
    constructor Create(const Name: string);

    property Name: string read FName;
  end;

  IConfig = class
  protected
    function GetString(Index: Integer): string;
    procedure SetString(Index: Integer; const Value: string);
  end;

  TConfig = class(IConfig)
  public
    [Group('Person')]
    property Name: string index 0 read GetString write SetString;
    [Group('Person')]
    property City: string index 1 read GetString write SetString;
  end;

implementation

{ Group }

constructor Group.Create(const Name: string);
begin
  FName := Name;
end;

{ IConfig }

function IConfig.GetString(Index: Integer): string;
begin
  case Index of
    0: begin // Name
    ...
    end;
    1: begin // City
      ...
    end;
    ...
  end;
end;

procedure IConfig.SetString(Index: Integer; const Value: string);
begin
  // same as above
end;

end.

      



If the getter / setter needs to know the name of a property, you can use RTTI to find the property that has a matching value index

, and if found, you can also access its attributes, for example:

function GetPropNameAndGroup(Cls: TClass; PropIndex: Integer; var PropName, GroupName: String): Boolean;
var
  Ctx: TRttiContext;
  Prop: TRttiProperty;
  Attr: TCustomAttribute;
begin
  PropName := '';
  GroupName := '';
  Ctx := TRttiContext.Create;
  for Prop in Ctx.GetType(Cls).GetProperties do
  begin
    if (Prop as TRttiInstanceProperty).Index = PropIndex then
    begin
      PropName := Prop.Name;
      for Attr in Prop.GetAttributes do
      begin
        if Attr is Group then
        begin
          GroupName := Group(Attr).Name;
          Break;
        end;
      end;
      Break;
    end;
  end;
  Result := (PropName <> '') and (GroupName <> '');
end;

function IConfig.GetString(Index: Integer): string;
var
  PropName, GroupName: string;
begin
  if GetPropNameAndGroup(ClassType, Index, PropName, GroupName) then
  begin
    //...
  end;
end;

procedure IConfig.SetString(Index: Integer; const Value: string);
var
  PropName, GroupName: string;
begin
  if GetPropNameAndGroup(ClassType, Index, PropName, GroupName) then
  begin
    //...
  end;
end;

      

+6


source


The getter or setter attribute has no way of determining which property was accessed, resulting in the getter or setter being called.

The consequence of this statement is that what you are asking to do is impossible.



As for how to solve your main problem, I wouldn't think too much about it. It's possible that property index access specifiers might be useful to you. But without knowing the real problem you are facing, I don’t feel ready to go into more detail.

0


source







All Articles