2

I want to use attributes for properties, but these attributes can change in the inherited class occasionaly. Here is a sample code (very simplified):

  TBaseClass = class(TObject)
  private
    FFoo: string;
  published
    [BaseAttirib('hello')]
    property Foo: string read FFoo;
  end;

  TChildClass = class(TBaseClass)
  published
    [BaseAttirib('good bye')]
    property Foo;
  end;

When Im going thru the properties, using RTTI, the Foo property appears twice in the GetProperties array:

var
  xObj: TObject;
  xType: TRttiType;
  xProp: TRttiProperty;
begin
  // FContext is a TRttiContext, already created, not included in this sample
  xObj := TChildClass.Create;
  try
    xType := FContext.GetType(xObj.ClassType);

    for xProp in xType.GetProperties do
    begin
      if not (xProp.Visibility in [mvPublished]) then
        Continue;

      Memo1.lines.add(xProp.Name + ' = ' + xProp.GetValue(xObj).AsString);
    end;
  finally
    FreeAndNil(xObj);
  end;
end;

Here is the output:

  Foo = TChildClass
  Foo = TChildClass

Changing property visibility to public in the base class would solve the problem, but that is not acceptable in my case, because I would have to increase this property visibility to published in all the child classes one by one.

My quesion is how can I prevent the Foo property duplicated appearance, or at least is there any way to decide between these duplications which one came from the base class and which one from the child?

UPDATE, little bit more explanation:

We have an algorytm which saves an object's all published properties in to an export file and the BaseAttrib attribute holds some neccessary information. Let's say I have an instance of TBaseClass and an instance of TChildClass and for the TBaseClass I want to see 'hello' in the output file and for the TChildClass I want to see 'good bye' in the output file.

Also worth to mention, if I wouldn't make instance of TBaseClass and lower the Foo visibility to public and then introduce a new instancable class where I publish the Foo property, I would lose the attribute for the Foo property (introduced in TBaseClass). So if I have 100 descendant classes but I just want to change the attribute value in only one class I would still have to copy the same attribute (with the original param) to the remaining 99 classes.

I would like to point that, this is a very simplified example, I have to introduce the attributes in an existing, complex class structure, and I want to do that with the least paintfull way without changing/rewrinting all the class inheritances.

If I could avoid/manage the property duplications that would be the best way, thats why Im looking for that kind of solution.

tcxbalage
  • 696
  • 1
  • 10
  • 30
  • 1
    Did you see https://stackoverflow.com/questions/63983114/why-are-some-properties-repeated-when-trtticontext-gettype-is-called-on-a-vcl-co? – Andreas Rejbrand Jan 31 '21 at 16:44
  • 1
    Don't publish it twice is the usual approach – David Heffernan Jan 31 '21 at 16:44
  • @Andreas Rejbrand: no, I haven't yet, have a look soon, thank you! – tcxbalage Jan 31 '21 at 16:59
  • @David Heffernan: thanks! that makes me a sad panda. Im curious how the IDE Object Inspetor solves this problem? – tcxbalage Jan 31 '21 at 16:59
  • @Andreas Rejbrand: that topic describes the same problem, but only explain why this is happening, but wont provide solution. anyhow, it was helpful, thanks again. – tcxbalage Jan 31 '21 at 17:08
  • @tcxbalage: Doesn't it help you to "decide between these duplications which one came from the base class and which one from the child"? – Andreas Rejbrand Jan 31 '21 at 17:10
  • @Andreas Rejbrand: yes, if I could decide wich one it the top level if there is multiple instances, that would be a solution, but I don't know how to do that :) should I enumarate thru all the parent classes and check if the property is already exists there? sounds complicated, but can be achieved. – tcxbalage Jan 31 '21 at 17:23
  • Can I recommend that you only publish once. Simple solution. – David Heffernan Jan 31 '21 at 17:24
  • @David Heffernan: that would be a viable solution, but sometimes in descendant classes I have to change the property's attribute, and when that is happen the duplications appears. I still could do that and only introduce the attributes on the top levels, but in this case I have to copy the same attributes to each top level class. – tcxbalage Jan 31 '21 at 17:40
  • and also would be a problem when I introduce for example a TGrandChilClass and I want different attribute parameters to give them. – tcxbalage Jan 31 '21 at 17:52
  • I updated the problem description, to be more clear. – tcxbalage Jan 31 '21 at 18:09

1 Answers1

2

My quesion [sic] is how can I prevent the Foo property duplicated appearance, or at least is there any way to decide between these duplications which one came from the base class and which one from the child?

Deciding which property comes from which class is actually trivial. Here's an extract from the documentation for TRttiType.GetProperties:

The list returned by GetProperties is ordered by the class/interface hierarchy. This means that the most recently included properties are located at the top of the list.

Hence, the first one is from the child class.

As an example, consider

type
  TestAttribute = class(TCustomAttribute)
    Value: string;
    constructor Create(S: string);
  end;

  TBaseClass = class(TObject)
  protected
    function GetFoo: string; virtual;
  published
    [Test('x')]
    property Foo: string read GetFoo;
  end;

  TChildClass = class(TBaseClass)
  published
    [Test('y')]
    property Foo;
  end;

Then

function GetTestValueOfFoo(AObject: TBaseClass): string;
begin
  var LRttiType := TRttiContext.Create.GetType(AObject.ClassType);
  if Assigned(LRttiType) then
    for var LRttiProp in LRttiType.GetProperties do
      if LRttiProp.Name = 'Foo' then
        for var LAttrib in LRttiProp.GetAttributes do
          if LAttrib is TestAttribute then
            Exit(TestAttribute(LAttrib).Value);
  Result := '';
end;

will return the value of the closest Test attribute in the class hierarchy. So,

var obj := TChildClass.Create;
try
  ShowMessage(GetTestValueOfFoo(obj))
finally
  obj.Free;
end;

shows y, but if you remove the Test attribute from the child class's Foo property, you will instead get x, because then that has become the closest value. GetTestValueOfFoo returns the empty string if no ancestor has the value set.

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • you are right, I didn't notice the sort before the output. – Remy Lebeau Jan 31 '21 at 18:45
  • ahh, the GetProperties is sorted by classes! I completely missed that info, thank you so much! I had a look in the earlier discussed topic where the output sorted alphabetically, but I missed that so I wasn't even think to check the order in then GetProperties array. – tcxbalage Jan 31 '21 at 18:48