分享

Using Attributes and TCustomAttribute descendants

 quasiceo 2013-12-12

Using Attributes and TCustomAttribute descendants

With Delphi 2010 attributes have been added as a language feature. They have been available in Delphi Prism (Targets .NET) and this now adds similar functionality to Win32.

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

11 comments:

  1. Thank you for illustrating how "noisy" code quickly becomes with attributes. :)

    This 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.

    Reply
  2. As with all launguage features you need to use them in the correct places.

    However, I don't think they lead to harder to read code. It's different but it's not harder to read. I believe they can lead to cleaner easier to maintain code, when used correctly.

    Reply
  3. Your TCustomAttribute descendents basically contain values only. Can they have methods that do something else, like popping up a message dialog, as well? Do you think that would make sense?

    Reply
  4. You can have additional functionality on your attributues. They are just classes.

    One big thing I neglected is you should avoid doing any validation at the time of the construction, by raising an exception. Instead you should do it querying the object that uses the Attributes.

    This way you have context and a class stack that makes sense. The Attribute Contruction will show you in deep in the rtti.pas and be confusing to debug. There are also other reasons, you can see them by reading the comments in RTTI.pas.

    Reply
  5. Thanks!
    I look forward to studying the rest of your blog posts.

    Reply
  6. .NET attributes allow you to set property values on the attribute, as well as pass constructor parameters. You didn't specifically show an example, but I assume the Delphi/Win32 attributes can do this too?

    Reply
  7. I wondered the same, but I have not seen anything in the documentation, or indication that properties setting is currently supported.

    Neither is the AttributeUsage although I did roll a runtime version of it together. Just nothing at compile time.

    Reply
  8. Me understanding the attribute-concept halts on one reason. I still haven't found a place where it is useful to me. I don't want to stress you but you did mentioned coming articles of where custom attributes are practical.
    P.S. Great articles about the RTTI - a much overlooked topic.

    Reply
  9. Many of these use Attributes.

    http://robstechcorner./2009/10/rtti-practical-examples.html

    Reply
  10. On first code snippet, lines 38-40; You are adding an attribute to a variable.

    Is this really possible? Is there a way to get a reference to the variable itself and read the attribute?

    Reply
  11. Hi Robert, maybe I'm wrong, but is the third example should not be in place:
    TAttrTest2 = class (TObject)
    here is
    TAttrTest2 = class (TCustomAttribute)

    Reply

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多