Attributes are a way of associating additional metadata information with a given type or member of a type.
They can be applied in many places, the following code shows several of the places you can place attributes.
01.
// Declaring an attribute.
02.
TAttrTest =
class
(TCustomAttribute)
03.
end
;
04.
05.
// Places you can use an attribute.
06.
[TAttrTest]
07.
TRec = Record
08.
[TAttrTest]
09.
value : String;
10.
[TAttrTest]
11.
procedure
DoThis([TAttrTest]arg1: String);
12.
End;
13.
14.
[TAttrTest]
15.
TMyEnum = (enOne,enTwo,enThree);
16.
17.
[TAttrTest]
18.
TMySet =
set
of
TMyEnum;
19.
20.
[TAttrTest]
21.
TObj =
class
(TObject)
22.
private
23.
[TAttrTest]
24.
FID: Integer;
25.
public
26.
[TAttrTest]
27.
FName : String;
28.
[TAttrTest]
29.
property
Id : Integer read FID
write
FID;
30.
[TAttrTest]
31.
constructor
Create;
32.
[TAttrTest]
33.
destructor
Destroy; override;
34.
[TAttrTest]
35.
procedure
DoThis;
36.
end
;
37.
38.
var
39.
[TAttrTest]
40.
I : Integer;
So how do attributes work in Delphi 2010?
Attributes must descend from TCustomAttribute, if you look at the declaration of TCustomAttribute you will find that there is nothing special.
1.
{ The base class for all custom attributes. Attribute
2.
instances created by the RTTI unit are owned by those
3.
members to which they apply. }
4.
TCustomAttribute =
class
(TObject)
5.
end
;
Passing just the name of the new attribute is only practical in a few cases, usually
you need additional data associated. This is done through the constructor. The following example shows how to setup the call to the constructor in the attribute.
01.
Type
02.
TAttrTest2 =
class
(TObject)
03.
private
04.
FId : Integer;
05.
public
06.
constructor
Create(aID : Integer);
07.
property
ID : Integer read FID
write
FID;
08.
end
;
09.
10.
[TAttrTest2(
123
)]
11.
TMyObject = Class(TObject)
12.
end
;
So its simple to declare an Attribute and decorate your types with them. Accessing the attributes stored in a given type involves using rtti.pas, I covered some of the basics of how this works in the previous post
Anything that can have attributes has an associated .GetAttributes() method that returns
the array of the attributes associated with that code.
The following code shows how to access the attributes.
01.
program
Project10;
02.
03.
{$APPTYPE CONSOLE}
04.
05.
uses
06.
SysUtils, RTTI;
07.
type
08.
TAttrTest2 =
class
(TCustomAttribute)
09.
private
10.
FId : Integer;
11.
public
12.
constructor
Create(aID : Integer);
13.
property
ID : Integer read FID
write
FID;
14.
end
;
15.
16.
[TAttrTest2(
1
)]
17.
[TAttrTest2(
2
)]
18.
[TAttrTest2(
3
)]
19.
TMyObject = Class(TObject)
20.
end
;
21.
22.
{ TAttrTest2 }
23.
24.
constructor
TAttrTest2
.
Create(aID: Integer);
25.
begin
26.
FID := aId;
27.
end
;
28.
29.
var
30.
c : TRttiContext;
31.
t : TRttiType;
32.
a : TCustomAttribute;
33.
begin
34.
c := TRttiContext
.
Create;
35.
try
36.
t := c
.
GetType(TMyObject);
37.
for
a
in
t
.
GetAttributes
do
38.
begin
39.
Writeln((a
as
TAttrTest2).ID);
40.
end
;
41.
finally
42.
c
.
Free
43.
end
;
44.
readln;
45.
end
.
Output:
1
2
3
Attributes also have a few other special items that the compiler implements.
If you have an attribute named like this...
01.
type
02.
TestAttribute =
class
(TCustomAttribute)
03.
end
;
04.
05.
It can be refered
to
in
two different ways
06.
07.
[TestAttribute]
08.
TExample =
class
(Tobject)
09.
end
;
10.
11.
[Test]
12.
TExample2 =
class
(TObject)
13.
end
;
The compiler will look for the type, if it is not found it will automatically append "Attribute" to the name and search again. This is done to mimic the .NET behavior.
The compiler also has some special support for types allow you to get TRttiType
easily from the pTypeInfo pointer. The following code segment shows how that pTypeInfo can be interchanged for TRttitype in attributes.
01.
uses
02.
SysUtils, Rtti;
03.
04.
type
05.
TestAttribute =
class
(TCustomAttribute)
06.
public
07.
constructor
Create(aType : TRttiType);
08.
end
;
09.
10.
[Test(typeinfo(Integer))]
11.
TEmployee =
class
(TObject)
12.
end
;
There are many practical applications for Attributes, I will explore many of these in later articles.
RTTI Article List
Thank you for illustrating how "noisy" code quickly becomes with attributes. :)
ReplyThis is another one of those language features that appeals to the laziness inherent in most developers.
We constantly seek to create code as quickly as possible, neglecting the fact that unless the code is "throw away", it will spend 99.9% of its life being maintained, not created.
Attributes make reading code physically harder by adding noise and conflating concerns (SQL attributes mixed in with test framework attributes, mixed in with xyz attributes and scattered through the declarations).
I'm not saying that attributes don't have some practical use, only that caution should be exercised in their use.
Where there is an alternative approach that keeps unrelated concerns separated and out of the declarations where they have no business being, that approach should be preferred, even if it takes a little longer to create that code.
You will reap the rewards in having something much more maintainable for the many months and years that follow those exciting few minutes of churning out some new code.