Sign in Join
Wiki copytemporary

Custom Attributes: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11Custom 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).
22
33**Currently (as of October 2020) the feature is available only in FPC trunk (aka 3.3.1)**
44
55## What can attributes be used for?
66
77An 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.
88
99## How are attributes declared?
1010
1111Attributes 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).
1212
1313**Important**: If you want to use your attribute type without any arguments then you *must* declare a parameterless constructor yourself as the one of *TCustomAttribute* is private.
1414
1515## How are attributes used?
1616
1717Attributes 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.
1818
1919Attribute clauses are only available if the new modeswitch PREFIXEDATTRIBUTES is set, which is the default in mode Delphi and DelphiUnicode.
2020
2121The syntax of an attribute clause is as follows:
2222
23-`ATTRIBUTECLAUSE::='[' ATTRIBUTELIST ']'`\
24-`ATTRIBUTELIST::=ATTRIBUTE [, ATTRIBUTELIST ]`\
25-`ATTRIBUTE::=IDENTIFIER [ ( PARAMLIST ) ]`\
26-`PARAMLIST::=CONSTEXPR [, PARAMLIST ]`
23+``` pascal
24+ATTRIBUTECLAUSE::='[' ATTRIBUTELIST ']'
25+ATTRIBUTELIST::=ATTRIBUTE [, ATTRIBUTELIST ]
26+ATTRIBUTE::=IDENTIFIER [ ( PARAMLIST ) ]
27+PARAMLIST::=CONSTEXPR [, PARAMLIST ]
28+```
2729
2830The 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:
2931
3032```delphi
3133
3234program tcustomattr;
3335
3436{$mode objfpc}{$H+}
3537{$modeswitch prefixedattributes}
3638
3739type
3840 TMyAttribute = class(TCustomAttribute)
3941 constructor Create;
4042 constructor Create(aArg: String);
4143 constructor Create(aArg: TGUID);
4244 constructor Create(aArg: LongInt);
4345 end;
4446
4547 {$M+}
4648 [TMyAttribute]
4749 TTestClass = class
4850 private
4951 fTest: LongInt;
5052 published
5153 [TMyAttribute('Test')]
5254 property Test: LongInt read fTest;
5355 end;
5456 {$M-}
5557
5658 [TMyAttribute(1234)]
5759 [TMy('Hello World')]
5860 TTestEnum = (
5961 teOne,
6062 teTwo
6163 );
6264
6365 [TMyAttribute(IInterface), TMy(42)]
6466 TLongInt = type LongInt;
6567
6668constructor TMyAttribute.Create;
6769begin
6870end;
6971
7072constructor TMyAttribute.Create(aArg: String);
7173begin
7274end;
7375
7476constructor TMyAttribute.Create(aArg: LongInt);
7577begin
7678end;
7779
7880constructor TMyAttribute.Create(aArg: TGUID);
7981begin
8082end;
8183
8284begin
8385
8486end.
8587```
8688
8789### Querying attributes
8890
8991Attributes can be accessed by both the TypInfo and Rtti units.
9092
9193For the TypInfo unit the ways to access attributes are as follows:
9294
9395For types:
9496
9597- use the AttributesTable field in TTypeData
9698- use GetAttributeTable on a PTypeInfo
9799- use GetAttribute on the attribute table together with an index to get a TCustomAttribute instance
98100
99101For properties:
100102
101103- use the AttributesTable of TPropInfo
102104- use GetAttribute on the attribute table together with an index to get a TCustomAttribute instance
103105- use GetPropAttribute on the PPropInfo together with an index to get a TCustomAttribute instance
104106
105107For the Rtti unit the ways to access attributes are as follows:
106108
107109For types:
108110
109111- use GetAttributes on the TRttiType of the type in question
110112
111113For properties:
112114
113115- use GetAttributes on the TRttiProperty of the property in question
114116
115117## The attributes feature's compatibility with Delphi
116118
117119The 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.
118120
119121FPC'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.
120122
121123Additionally 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:
122124
123125```delphi
124126procedure Test; [cdecl];
125127begin
126128end;
127129```
128130
129131## Complete example
130132
131133```Delphi
132134program testattributes;
133135{
134136 This is a simple example on how to use custom attributes.
135137 The class uses a custom attribute to retrieve a static date
136138 at program start-up. This is just for the purpose of demo.
137139 This demo's:
138140 - Creation
139141 - Decoration
140142 - Retrieval
141143}
142144
143145{$mode delphi}{$H+}{$M+}
144146{$warn 5079 off} { turn warning experimental off }
145147uses
146148 sysutils, typinfo, rtti, classes;
147149
148150type
149151 {A custom attribute to decorate a class with a certain date }
150152 ADateTimeAttribute = class(TCustomAttribute)
151153 private
152154 FArg:TDateTime;
153155 public
154156 { Just to show a Custom Attribute can have mutiple constructors }
155157 constructor Create(const aArg: TDateTime);overload;
156158 { We can use the predefined %DATE% compiler include
157159 In the context of a custom attribute we need a
158160 constant expression for the default parameter }
159161 constructor Create(const aArg: String = {$I %DATE%});overload;
160162 published
161163 property Date:TDateTime read Farg;
162164 end;
163165
164166 { A datetime class that is decorated with our custom attribute }
165167 { Note you can leave out 'Attribute', the compiler resolves it }
166168 { [ADateTime(21237.0)] uses first constructor,displays some date in 1958 }
167169
168170 {This calls the second constructor }
169171 [ADateTime]
170172 TMyDateTimeClass = class
171173 private
172174 FDateTime:TDateTime;
173175 public
174176 constructor create;
175177 published
176178 [ADateTime]
177179 property Day:TDateTime read FDatetime write FdateTime;
178180 end;
179181
180182 constructor ADateTimeAttribute.Create(const aArg: TDateTime);
181183 begin
182184 FArg := aArg;
183185 end;
184186
185187 constructor ADateTimeAttribute.Create(const aArg: string );
186188 var
187189 MySettings:Tformatsettings;
188190 begin
189191 { set up the date format according to how
190192 the compiler formats %DATE% include }
191193 MySettings :=DefaultFormatSettings;
192194 MySettings.ShortDateFormat:='yyyymmdd';
193195 MySettings.DateSeparator :='/';
194196 { Now convert }
195197 FArg := StrToDateTime(aArg, MySettings);
196198 end;
197199
198200 { We query the rtti to set the value }
199201 constructor TMyDateTimeClass.Create;
200202 var
201203 Context : TRttiContext;
202204 AType : TRttiType;
203205 Attribute : TCustomAttribute;
204206 begin
205207 inherited;
206208 Context := TRttiContext.Create;
207209 try
208210 AType := Context.GetType(typeinfo(TMyDateTimeClass));
209211 for Attribute in AType.GetAttributes do begin
210212 if Attribute is ADateTimeAttribute then
211213 FDateTime := ADateTimeAttribute(Attribute).Date;
212214 end;
213215 finally
214216 Context.Free
215217 end;
216218 end;
217219
218220
219221var
220222 Test:TMyDateTimeClass;
221223begin
222224 Test := TMyDateTimeClass.Create;
223225 try
224226 writeln('Compile date is :',DateTimeToStr(Test.Day));
225227 finally
226228 test.free;
227229 end;
228230end.
229231```
230232
231233## See Also
232234
233235- [[FPC New Features Trunk]]
234236- <http://docwiki.embarcadero.com/RADStudio/XE6/en/Overview_of_Attributes> - Delphi Attributes reference
235237- <http://delphi.about.com/od/oopindelphi/a/delphi-attributes-understanding-using-attributes-in-delphi.htm> Delphi Tutorial on property attributes
236238
237239---
238240*From the Free Pascal wiki, page "Custom Attributes", as it stood on 2022-12-30 after a change by Zoltanleo, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
239241Category: FPC

Back to history