Extracting Attributes at Run Time
Go Up to Attributes (RTTI)
Provides information about run-time aspects of attributes--how to extract them and how to make custom decisions based on their informational value.
Attribute Instantiation
Annotation (as discussed in Annotating Types and Type Members) is a simple method of attaching an attribute to a type or a member. The information included into the compiled binary only includes the class of the attribute, the pointer to the selected constructor, and the list of constants that are passed to the attribute's constructor at instantiation time.
The actual instantiation of attributes happens when the consumer code queries for them in a given type or type member. This means that instances of attribute classes are not created automatically, but rather when the program explicitly searches for them. There is no guaranteed order in which attributes are instantiated, nor it is known how many instances are created. A program should not depend on such consequences.
Consider the following attribute declaration:
type TSpecialAttribute = class(TCustomAttribute) public FValue: String; constructor Create(const AValue: String); end; constructor TSpecialAttribute.Create(const AValue: String); begin FValue := AValue; end;
The TSpecialAttribute is then used as annotation in the following example:
type [TSpecialAttribute('Hello World!')] TSomeType = record ... end;
To extract the attribute from the TSomeType type, the user code must employ the functionality exposed by the System.Rtti unit. The following example demonstrates the extraction code:
var LContext: TRttiContext; LType: TRttiType; LAttr: TCustomAttribute; begin { Create a new Rtti context } LContext := TRttiContext.Create { Extract type information for TSomeType type } LType := LContext.GetType(TypeInfo(TSomeType)); { Search for the custom attribute and do some custom processing } for LAttr in LType.GetAttributes() do if LAttr is TSpecialAttribute then Writeln(TSpecialAttribute(LAttr).FValue); { Destroy the context } LContext.Free; end;
As seen in the example above, the user must specifically write code to query for attributes annotated to a type. The actual attribute instances are created in the TRttiType.GetAttributes method. Note that the example does not destroy the instances; the TRttiContext frees all resources afterward.
Exceptions
Because the actual instantiation of attributes is performed in the user code, one must be aware of the possible exceptions that may occur in the attributes' constructors. The general recommendation is to use a try .. except clause surrounding the code that queries for attributes.
To exemplify the problem, the attribute constructor in the original example is changed to look like this:
constructor TSpecialAttribute.Create(const AValue: String); begin if AValue = '' then raise EArgumentException.Create('Expected a non-null string'); FValue := AValue; end;
and the annotation for TSomeType is changed to pass an empty string to the attribute constructor:
type [TSpecialAttribute('')] TSomeType = record ... end;
In this case, the code that queries for the attributes of type TSomeType will fail with an EArgumentException exception, which is raised by the instantiating attribute. The recommendation is to change the query code to use the try .. except clause:
{ Search for the custom attribute and do some custom processing } try for LAttr in LType.GetAttributes() do if LAttr is TSpecialAttribute then Writeln(TSpecialAttribute(LAttr).FValue); except { ... Do something here ... } end;