Difference between revisions of "Custom Attributes"
m (minor spelling and grammatical improvements) |
|||
Line 1: | Line 1: | ||
− | Custom Attributes allow you to decorate | + | Custom Attributes currently allow you to decorate type definitions and |
published properties of classes with additional metadata that can be | published properties of classes with additional metadata that can be | ||
− | queried using | + | queried using RTTI (runtime type information). |
− | |||
− | |||
+ | Currently the feature is available only in FPC trunk | ||
==What can attributes be used for?== | ==What can attributes be used for?== | ||
− | + | An attribute is used to associate specific metadata with a class. For example you can use an attribute to mark a class with the name of its corresponding database table, | |
− | or | + | or to annotate a web service class with a string identifying its base path. |
==How are attributes declared?== | ==How are attributes declared?== | ||
Attributes are simply classes that descend from the new System type | Attributes are simply classes that descend from the new System type | ||
− | TCustomAttribute. The | + | TCustomAttribute. The constructors of such TCustomAttribute descendants are the most important feature to implement since they are used to pass additional parameters to the attribute (such as the associated database table name, or the base path in the above examples). |
− | |||
− | the table name or path). | ||
==How are attributes used?== | ==How are attributes used?== | ||
− | Attributes are bound to a type or property by | + | Attributes are bound to a type or property by specifying at least one |
− | attribute | + | attribute clause ahead of the type or property. For a type the attribute is specified in the |
− | + | type definition (such as a class, record, or enum declaration) or in a unique | |
type redeclaration (e.g. "TLongInt = type LongInt"). Mere type renames | type redeclaration (e.g. "TLongInt = type LongInt"). Mere type renames | ||
(e.g. "TLongInt = LongInt") are not allowed. | (e.g. "TLongInt = LongInt") are not allowed. | ||
Attribute clauses are only available if the new modeswitch | Attribute clauses are only available if the new modeswitch | ||
− | PREFIXEDATTRIBUTES is set which is the default in mode Delphi and | + | PREFIXEDATTRIBUTES is set, which is the default in mode Delphi and |
DelphiUnicode. | DelphiUnicode. | ||
− | The syntax of | + | The syntax of an attribute clause is as follows: |
ATTRIBUTECLAUSE::='[' ATTRIBUTELIST ']' | ATTRIBUTECLAUSE::='[' ATTRIBUTELIST ']' | ||
Line 34: | Line 31: | ||
PARAMLIST::=CONSTEXPR [, PARAMLIST ] | PARAMLIST::=CONSTEXPR [, PARAMLIST ] | ||
− | The IDENTIFIER is | + | The IDENTIFIER is the name of the attribute class. |
− | attribute class | + | If you name the attribute class to end with "Attribute" (the case of the "attribute" suffix is immaterial) then the name may be used subsequently without the "Attribute" suffix. So TMyAttribute and TMy are equivalent alternatives in the following example: |
− | then the name may be used without the "Attribute" suffix. | ||
− | |||
− | |||
<source lang="delphi"> | <source lang="delphi"> | ||
Line 111: | Line 105: | ||
For properties: | For properties: | ||
* use the AttributesTable of TPropInfo | * use the AttributesTable of TPropInfo | ||
− | * use GetAttribute on the attribute table together with | + | * use GetAttribute on the attribute table together with an index to get a TCustomAttribute instance |
− | * use GetPropAttribute on the PPropInfo together with an index to get a TCustomAttribute | + | * use GetPropAttribute on the PPropInfo together with an index to get a TCustomAttribute instance |
For the Rtti unit the ways to access attributes are as follows: | For the Rtti unit the ways to access attributes are as follows: | ||
Line 122: | Line 116: | ||
* use GetAttributes on the TRttiProperty of the property in question | * use GetAttributes on the TRttiProperty of the property in question | ||
− | == | + | ==The attributes feature's compatibility with Delphi== |
− | The feature itself is Delphi compatible | + | The feature itself is Delphi compatible with the proviso that FPC is much more |
− | unforgiving regarding unbound properties | + | unforgiving regarding unbound properties. If the attribute class is not |
known or the attribute clauses are not bound to a valid type or property | known or the attribute clauses are not bound to a valid type or property | ||
the compiler will generate an error. | the compiler will generate an error. | ||
− | + | FPC's RTTI is not considered Delphi compatible. However it covers the | |
− | same functionality. | + | same functionality. In contrast to Delphi (which uses Invoke to create the |
− | attribute instance FPC uses a constructor function | + | attribute instance) FPC uses a constructor function. FPC's implementation has the |
− | advantage | + | advantage of working on systems that don't have full Invoke support. |
Additionally using the PREFIXEDATTRIBUTES modeswitch disables the | Additionally using the PREFIXEDATTRIBUTES modeswitch disables the | ||
− | directive clauses for functions, methods and procedure/method types | + | directive clauses for functions, methods and procedure/method types. |
− | The following is not allowed | + | The following is not allowed any more with the PREFIXEDATTRIBUTES modeswitch enabled: |
<source lang="delphi"> | <source lang="delphi"> |
Revision as of 22:48, 13 July 2019
Custom Attributes currently allow you to decorate type definitions and published properties of classes with additional metadata that can be queried using RTTI (runtime type information).
Currently the feature is available only in FPC trunk
What can attributes be used for?
An attribute is used to associate specific metadata with a class. For example you can use an attribute to mark a class with the name of its corresponding database table, or to annotate a web service class with a string identifying its base path.
How are attributes declared?
Attributes are simply classes that descend from the new System type TCustomAttribute. The constructors of such TCustomAttribute descendants are the most important feature to implement since they are used to pass additional parameters to the attribute (such as the associated database table name, or the base path in the above examples).
How are attributes used?
Attributes are bound to a type or property by specifying at least one attribute clause ahead of the type or property. For a type the attribute is specified in the type definition (such as a class, record, or enum declaration) or in 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 an attribute clause is as follows:
ATTRIBUTECLAUSE::='[' ATTRIBUTELIST ']' ATTRIBUTELIST::=ATTRIBUTE [, ATTRIBUTELIST ] ATTRIBUTE::=IDENTIFIER [ ( PARAMLIST ) ] PARAMLIST::=CONSTEXPR [, PARAMLIST ]
The IDENTIFIER is the name of the attribute class. If you name the attribute class to end with "Attribute" (the case of the "attribute" suffix is immaterial) then the name may be used subsequently without the "Attribute" suffix. So TMyAttribute and TMy are equivalent alternatives in 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 an index to get a TCustomAttribute instance
- use GetPropAttribute on the PPropInfo together with an index to get a TCustomAttribute instance
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
The attributes feature's compatibility with Delphi
The feature itself is Delphi compatible with the proviso that 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.
FPC's RTTI is not considered Delphi compatible. However it covers the same functionality. In contrast to Delphi (which uses Invoke to create the attribute instance) FPC uses a constructor function. FPC's implementation has the advantage of working 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 any more with the PREFIXEDATTRIBUTES modeswitch enabled:
procedure Test; [cdecl];
begin
end;