Forum > General

Some observations on the use of RTTI

<< < (2/14) > >>

vfclists:

--- Quote from: egsuh on October 07, 2024, 08:09:47 am ---RTTI can be linked to any property, calling its setter and getter even if there are no corresponding field. Is this what you are wondering?

--- End quote ---

Yes. Although it is logical when you think of it, it escaped me for a while and probably because most of the examples shown involved manipulating UI objects.

Do you know of any examples which are less UI oriented?

Thaddy:

--- Quote from: vfclists on October 07, 2024, 06:20:34 pm ---ink? Searching for "rtti console" is not revealing it.

--- End quote ---
https://wiki.freepascal.org/Custom_Attributes

The attributes are still being worked on. i can probably simpyfy that example a bit.
And clarify that with the same attribute used like this, you get the day I was born:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---[ADateTime(21237.0)]You can see that the other constructor is called.
Attributes can have multiple constructors, so you can add what you need.

Another example I have is where you annotate a TBufDataSet with field attributes, so you can set e.g. FieldDefs at runtime from values that are defined as attributes at compile time.
You can change the main program body of the wiki examle to this and you can see there are more than one way to query the attribute value:
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} --- //adapted from an unrelated Embarcadero example// rest of the code stays as is.var  LContext: TRttiContext;  LType: TRttiType;  LAttr: TCustomAttribute;begin  { Create a new Rtti context }  LContext := TRttiContext.Create;    { Extract type information for TMyDateTimeClass }  LType := LContext.GetType(TypeInfo(TMyDateTimeClass));  { Search for the custom attribute and do some custom processing }  for LAttr in LType.GetAttributes() do    if LAttr is ADateTimeAttribute then      Writeln(DateTimeToStr(ADateTimeAttribute(LAttr).FArg));   { Destroy the context }  LContext.Free;end.

Warfley:

--- Quote from: vfclists on October 07, 2024, 06:28:18 pm ---Do you know of any examples which are less UI oriented?

--- End quote ---
There are two man use cases for RTTI. First the most obvious one is data serialization and deserialization. For example, if you want to take a record and serialize it into json, right now you have to do it like this:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---jsonObject.Strings['MyField1'] := MyRec.MyField1;jsonObject.Integers['MyField2'] := MyRec.MyField2;jsonObject.Arrays['MyArrayField'] := SerializeArray(MyRec.MyArrayField);Extremely annoying and error prone. With Extended RTTI, where field names are written into the RTTI, you could write a serializer that will just take all public fields and writes them down, recursively descending into other records or arrays. Then it would just be:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---SerializeRecord(@MyRec, TypeInfo(TMyRec));And when you update your record, rename fields, etc. the serializer will still work because of RTTI.

This is especially useful combined with the custom attributes, where you could do things like that:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---type  TMyRecord = record    [SerializeName('Field1'), SerializeOptional('Default')]    FField1: String;    [SkipSerialize]    FHiddenField: Integer;  //...so SerializeName would be an attribute to have a custom name, e.g. for backwards compatibility, or to not have the leading F as in this case. SerializeOptional could be an attribute to say when deserializing and this object is not there, fill it with 'Default'. SkipSerialize would tell the serializer that FHiddenField is an internal field not to be serialized into json.

This way you do not need to write a single line of (error prone) code to convert your data into other formats, as all the required information can be encoded into the definition of the type.

The second main use case is reflection. Basically altering the behavior of your code at runtime. For example you could have a module system where you scan your filesystem for a module (e.g. in form of a dynamic library) and if a new module is found the module is loaded and RTTI is used to e.g. load these function pointers into existing objects/classes at runtime without having to restart the application.

For example you could create a software where classes objects have logger fields, and on startup, the module loader would look if there is a logger module available, and if so, scan the classes, if any has a logger field, and dynamically load the logger into that field.

egsuh:

--- Quote ---https://wiki.freepascal.org/Custom_Attributes
--- End quote ---

In the second example of previous page, where does [ADateTime] come from? Should it be [ADateTimeAttribute]? I have added comments below.


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---program testattributes;   {$mode delphi}{$H+}{$M+}{$warn 5079 off} { turn warning experimental off }uses  sysutils, typinfo, rtti, classes; type   ADateTimeAttribute = class(TCustomAttribute)   private     FArg:TDateTime;   public     constructor Create(const aArg: TDateTime);overload;     constructor Create(const aArg: String = {$I %DATE%});overload;    published     property Date:TDateTime read Farg;   end;     {This calls the second constructor }   [ADateTime]     // <== Where does this come from? not [ADateTimeAttribute]?    TMyDateTimeClass = class   private     FDateTime:TDateTime;   public     constructor create;   published     [ADateTime]   // <== And this     property Day:TDateTime read FDatetime write FdateTime;   end; 

Thaddy:
That is explained higher up in the wiki and in my comments of the complete wiki example too: you can omit the attribute suffix part. One usually does.
The code is correct.
Clear? Otherwise, please ask what is not clear. This is also Delphi compatible.

Navigation

[0] Message Index

[#] Next page

[*] Previous page

Go to full version