Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

Introducing Attribute

delphi delphi attribute attribute introduction tcustomattribute

  • Please log in to reply
No replies to this topic

#1 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 06 September 2013 - 02:12 AM

Since version 2010, Delphi introduces a language feature called Attribute. Basically it is a way to enrich the information of a class or class members through simple annotations. Note that it might be possible that attribute is applicable to things other than class or class members, it's just that currently I only have experience with applying attributes on class and class members only. In a sense, attribute is a easy way to add custom information to RTTI (run time type information). Prior the introduction of Attribute I had done similar things, but in much more complicated way.
 
Let's say that you have a configuration class like this for your Golf club member management software:
 
TConfig=class
public
  property MinMemberAge: Integer ... ;
end;
Property MinMemberAge explains the minimum age on members that could be considered to sign up for the organization. In our application, users could change this according to their situations. However we want to ensure that they could not enter ridiculous values (like for example 1 year). Of course you can do the validation in the setter method. However it will be much easier to understand and maintain when we use the power of Attribute.
 
Let's say that we want to make sure that the minimum member age is 10. With Attribute, you will add a simple notation like this:
 
TConfig=class
public
  [MinValue(10)]
  property MinMemberAge: Integer ... ;
end;
From the above codes, we can easily understand that the minimum value of member MinMemberAge is 10. Of course the validation will not be happen magically. You need to write the codes to do the validation. So, how?
 
First you need to write an attribute class with three requirements.
  • descendant form TCustomAttribute
  • have proper name. In our example above, the proper name of the attribute class could be either MinValue or MinValueAttribute. More about the names later.
  • have constructor(s) with matching parameters as in the annotation(s).
1. Descendant of TCustomAttribute
 
I think there is no problem with this requirement. Especially since TCustomAttribute was declared in System unit. So it's accessible from anywhere.
 
 
2. Have Proper Name
 
The compiler will use the attribute class' name to match against the annotation. In the matching process, Delphi has an interesting behavior. When no full name match found, it will append Attribute to the end of the name used in the annotation and will do another search for that name. That is why in our example above, we could use either MinValue or MinValueAttribute. Technically I don't have any recommendation of which to use. However, personally I prefer to consistently use the Attribute suffix in most of my attribute classes.
 
 
So now for our example, we could declare the class either like this:

type
  MinValue=class(TCustomAttribute)
    ...
  end;
or this:
type
  MinValueAttribute=class(TCustomAttribute)
    ...
  end;


3. Have constructor(s) with matching parameters as in the annotation(s)
 
The annotation [MinValue(10)] actually tells the compiler how to create an instance of the corresponding attribute class. In that example, the compiler actually will interpret it into:

  MinValue.Create(10);
That is why we need for MinValue or MinValueAttribute class to have constructor that accepts value of 10. Since we know we are dealing with integer, then we need to have a constructor like this:
  constructor Create(const AMinValue: Integer);
Now that we have all the information, let's complete our implementation of the example attribute class.
interface

type
  MinValueAttribute=class(TCustomAttribute)
  private
    FMinValue: Integer;
  public
    constructor Create(const AMinValue: Integer);
    
    property MinValue: Integer read FMinValue;
  end;

implementation

constructor MinValueAttribute.Create(const AMinValue: Integer);
begin
  FMinValue := AMinValue;
end;
How to Use Attribute Information?
 
In run time, we can query the information set through attributes using RTTI. Once we have the information, it's up to us of what to do next.
 
Here is one example of how to query the attributes of a given object and its properties.

procedure QueryAttributesOf(AObj: TObject);
var
  vCtx: TRttiContext;      // rtti context of the whole system
  vType: TRttiType;        // the object's rtti information
  vProp: TRttiProperty;    // a property rtti information
  vAttr: TCustomAttribute; // an attribute
begin
  // Get a reference to our system's rtti context
  vCtx := TRttiContext.Create;
  try
    // get rtti information about our object's class
    vType := vCtx.GetType(AObj.ClassType);
    // check every attribute linked to the class
    for vAttr in vType.GetAttributes do
    begin
      // do anything here with the attribute linked to the object
    end;

    // query every properties of the class
    for vProp in vType.GetProperties do
      // check every attribute linked to a property
      for vAttr in vProp.GetAttributes do
      begin
        // do anything here with the attribute linked to the current property of the object
      end;
  finally
    vCtx.Free;
  end;
end;

Initially It might look complex. But in most cases, you only need to write this kind of codes once, and will be able to reuse it in many projects. I will prove it in next tutorial when I show you how to use attributes to easily store/reload information from ini file or registry.
 
In our current example, we want to compare attribute's minimum value with the current value of the property it linked to. When the property's value is lower that specified by the attribute, we will raise an exception. Let's implement this as a procedure named Validate. The implementation codes will be like this:
 
procedure Validate(AObj: TObject);
var
  vCtx: TRttiContext;      // rtti context of the system
  vType: TRttiType;        // the object's rtti information
  vProp: TRttiProperty;    // a property rtti information
  vAttr: TCustomAttribute; // an attribute
  vPropValue: Integer;
begin
  // Get a reference to our system's rtti context
  vCtx := TRttiContext.Create;
  try
    // get rtti information about our object's class
    vType := vCtx.GetType(AObj.ClassType);
    
    // query every properties of the class
    for vProp in vType.GetProperties do
      // check every attribute linked to a property
      for vAttr in vProp.GetAttributes do
        // if the attribute is of "MinValueAttribute" class, do further processing
        if vAttr is MinValueAttribute then
        begin
          // get the property value
          vPropValue := vProp.GetValue(AObj).AsInteger;
          // compare the property's value with the minimum value explained by the attribute
          // raise exception when the property's value is lower
          if vPropValue < MinValueAttribute(vAttr).MinValue then
            raise Exception.Create('Property ' + vProp.Name + ' has lower value (' + IntToStr(vPropValue) + ') that suggested (' + IntToStr(MinValueAttribute(vAttr).MinValue) + ')');
        end;
  finally
    vCtx.Free;
  end;
end;

Demo Project
 
I have written a demo project for this tutorial. It's attached and you can download it at the end of this tutorial.
  • Upon running you will get something like this:
    DemoAttributes_Runtime000.png
  • Now let's enter an invalid value (i.e. a value less than 10), for example 4. Like this.
    DemoAttributes_Runtime010_InvalidValue.png
  • Click the Validate! button and this is what we get:
    DemoAttributes_Runtime020_InvalidException.png
  • Now let's try valid value, i.e. values of at least 10. This time we will use 10, like this:
    DemoAttributes_Runtime030_ValidValue.png
  • Now click the Validate! button again, now we get confirmation that the value is okay. Like this:
    DemoAttributes_Runtime040_ValidValueConfirm.png
And here is the full source code of the demo project. DemoAttributes_Runtime030_ValidValue.png . Feel free to use it for any kind of purpose.

Next time we will use attribute in real action.

Attached Files


  • 0





Also tagged with one or more of these keywords: delphi, delphi attribute, attribute introduction, tcustomattribute

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download