Custom Attributes

From Lazarus wiki
Revision as of 18:21, 13 July 2019 by Skalogryz (talk | contribs) (How are attributes used?)

Custom Attributes allow you to decorate (currently) type definitions and published properties of classes with additional metadata that can be queried using the RTTI.

The feature is currently available in FPC trunk only

What can attributes be used for?

You can use them to mark classes with the name of its corresponding database table or the base path for a web service class.

How are attributes declared?

Attributes are simply classes that descend from the new System type TCustomAttribute. The important part are the constructors of the class. These can be used to pass additional parameters to the attribute (like the table name or path).

How are attributes used?

Attributes are bound to a type or property by using one or multiple attribute clauses in front of the type or property. For types it must be a type definition (e.g. a class, a record, an enum, etc.) or a unique type redeclaration (e.g. "TLongInt = type LongInt"). Mere type renames (e.g. "TLongInt = LongInt") are not allowed.

Attribute clauses are only available if the new modeswitch PREFIXEDATTRIBUTES is set which is the default in mode Delphi and DelphiUnicode.

The syntax of a attribute clause is the following:

ATTRIBUTECLAUSE::='[' ATTRIBUTELIST ']'
ATTRIBUTELIST::=ATTRIBUTE [, ATTRIBUTELIST ]
ATTRIBUTE::=IDENTIFIER [ ( PARAMLIST ) ]
PARAMLIST::=CONSTEXPR [, PARAMLIST ]

The IDENTIFIER is either the name of the attribute class as is or the attribute class' name can end in "Attribute" (casing irrelevant) and then the name may be used without the "Attribute" suffix.

Take the following example:

program tcustomattr;

{$mode objfpc}{$H+}
{$modeswitch prefixedattributes}

type
   TMyAttribute = class(TCustomAttribute)
     constructor Create;
     constructor Create(aArg: String);
     constructor Create(aArg: TGUID);
     constructor Create(aArg: LongInt);
   end;

   {$M+}
   [TMyAttribute]
   TTestClass = class
   private
     fTest: LongInt;
   published
     [TMyAttribute('Test')]
     property Test: LongInt read fTest;
   end;
   {$M-}

   [TMyAttribute(1234)]
   [TMy('Hello World')]
   TTestEnum = (
     teOne,
     teTwo
   );

   [TMyAttribute(IInterface), TMy(42)]
   TLongInt = type LongInt;

constructor TMyAttribute.Create;
begin
end;

constructor TMyAttribute.Create(aArg: String);
begin
end;

constructor TMyAttribute.Create(aArg: LongInt);
begin
end;

constructor TMyAttribute.Create(aArg: TGUID);
begin
end;

begin

end.

Querying attributes

Attributes can be accessed by both the TypInfo and Rtti units.

For the TypInfo unit the ways to access attributes are as follows:

For types:

  • use the AttributesTable field in TTypeData
  • use GetAttributeTable on a PTypeInfo
  • use GetAttribute on the attribute table together with an index to get a TCustomAttribute instance

For properties:

  • use the AttributesTable of TPropInfo
  • use GetAttribute on the attribute table together with a nindex to get a TCustomAttribute instance
  • use GetPropAttribute on the PPropInfo together with an index to get a TCustomAttribute instnace

For the Rtti unit the ways to access attributes are as follows:

For types:

  • use GetAttributes on the TRttiType of the type in question

For properties:

  • use GetAttributes on the TRttiProperty of the property in question

How is the compatibility of the attributes feature

The feature itself is Delphi compatible except FPC is much more unforgiving regarding unbound properties: if the attribute class is not known or the attribute clauses are not bound to a valid type or property the compiler will generate an error.

The RTTI however is not considered Delphi compatible, but it covers the same functionality. Contrary to Delphi which uses Invoke to create the attribute instance FPC uses a constructor function which has the advantage that it works on systems that don't have full Invoke support.

Additionally using the PREFIXEDATTRIBUTES modeswitch disables the directive clauses for functions, methods and procedure/method types: The following is not allowed anymore with the modeswitch enabled:

procedure Test; [cdecl];
begin
end;

See Also