Sign in Join
Wiki copytemporary

Custom Attributes/zh CN

English · Русский · 中文(中国)

自定义属性(Custom Attributes)目前允许您为类型定义和类的已发布属性添加额外的元数据,这些元数据可以通过RTTI(运行时类型信息)进行查询。

截至2020年10月,该功能仅在FPC主干版本(即3.3.1)中可用

属性可以用来做什么?

属性用于将特定的元数据与类相关联。例如,您可以使用属性来标记一个类,指定其对应的数据库表名,或者为Web服务类添加一个标识其基本路径的字符串。

如何声明属性?

属性只是从新的系统类型TCustomAttribute派生的类。实现此类TCustomAttribute后代的构造函数是最重要的特性,因为它们用于向属性传递额外的参数(如上述示例中的关联数据库表名或基本路径)。

重要 :如果您想在不使用任何参数的情况下使用您的属性类型,则必须自己声明一个无参数的构造函数,因为TCustomAttribute的构造函数是私有的。

如何使用属性?

通过将至少一个属性子句放在类型或属性之前,可以将属性绑定到类型或属性。对于类型,属性在类型定义(如类、记录或枚举声明)或唯一类型重声明(例如"TLongInt = type LongInt")中指定。仅允许类型重声明,不允许仅重命名类型(例如"TLongInt = LongInt")。

如果设置了新的模式开关PREFIXEDATTRIBUTES(在Delphi和DelphiUnicode模式下是默认的),则可用属性子句。

属性子句的语法如下:

pascal
ATTRIBUTECLAUSE::='[' ATTRIBUTELIST ']'
    ATTRIBUTELIST::=ATTRIBUTE [, ATTRIBUTELIST ]
    ATTRIBUTE::=IDENTIFIER [ ( PARAMLIST ) ]
    PARAMLIST::=CONSTEXPR [, PARAMLIST ]

其中,IDENTIFIER是属性类的名称。如果属性类的名称以"Attribute"结尾(不区分大小写),则后续可以省略"Attribute"后缀。因此,在以下示例中,TMyAttribute和TMy是等效的替代方案:

pascal
program tcustomattr;
    
    {$mode objfpc}{$H+}$
    {$modeswitch prefixedattributes}
    
    type
       TMyAttribute = class(TCustomAttribute)
         constructor Create;
         constructor Create(aArg: String);
         constructor Create(aArg: TGUID);
         constructor Create(aArg: LongInt);
       end;
    
       {$M+}
       [TMyAttribute]
       TTestClass = class
       private
         fTest: LongInt;
       published
         [TMyAttribute('Test')]
         property Test: LongInt read fTest;
       end;
       {$M-}
    
       [TMyAttribute(1234)]
       [TMy('Hello World')]
       TTestEnum = (
         teOne,
         teTwo
       );
    
       [TMyAttribute(IInterface), TMy(42)]
       TLongInt = type LongInt;
    
    constructor TMyAttribute.Create;
    begin
    end;
    
    // ... 其他构造函数实现 ...
    
    constructor TMyAttribute.Create(aArg: String);
    begin
    end;
    
    constructor TMyAttribute.Create(aArg: LongInt);
    begin
    end;
    
    constructor TMyAttribute.Create(aArg: TGUID);
    begin
    end;
    
    begin
    
    
    begin
    
    end.

查询属性

属性可以通过TypInfo和Rtti单元进行访问。

对于TypInfo单元,访问属性的方式如下:

对于类型:

对于属性:

对于Rtti单元,访问属性的方式如下:

对于类型:

对于属性:

属性特性与Delphi的兼容性

该特性本身与Delphi兼容,但FPC对未绑定属性的容忍度较低。如果属性类未知或属性子句未绑定到有效的类型或属性,编译器将生成错误。

FPC的RTTI不被认为是与Delphi兼容的,但它涵盖了相同的功能。与Delphi(使用Invoke创建属性实例)不同,FPC使用构造函数函数。FPC的实现优势在于它可以在没有完整Invoke支持的系统上工作。

此外,使用PREFIXEDATTRIBUTES模式开关会禁用函数、方法和过程/方法类型的指令子句。启用PREFIXEDATTRIBUTES模式开关后,以下用法不再允许:

pascal
procedure Test; [cdecl];
    begin
    
    end;

完整示例

pascal
program testattributes;
    {
      这是一个简单的示例,说明如何使用自定义属性。
      该类使用自定义属性在程序启动时检索静态日期。这仅用于演示。
      此演示包含:
      - 创建
      - 装饰
      - 检索
    }
       
    {$mode delphi}{$H+}{$M+}
    {$warn 5079 off} { 关闭实验性警告 }
    uses
      sysutils, typinfo, rtti, classes;
     
    type
       { 用于装饰类以指定特定日期的自定义属性 }
       ADateTimeAttribute = class(TCustomAttribute)
       private
         FArg:TDateTime;
       public
       { 展示自定义属性可以有多个构造函数 }
         constructor Create(const aArg: TDateTime);overload;
         { 我们可以使用预定义的%DATE%编译器包含
           在自定义属性的上下文中,我们需要一个
           常量表达式作为默认参数 }
         constructor Create(const aArg: String = {$I %DATE%});overload;
        published
         property Date:TDateTime read FArg;
       end;
     
       { 使用我们的自定义属性装饰的日期时间类     }
       { 注意:您可以省略'Attribute',编译器会解析它     }
       { [ADateTime(21237.0)] 调用第一个构造函数,显示1958年的某个日期 }
       
       { 这将调用第二个构造函数 }
       [ADateTime]
       TMyDateTimeClass = class
       private
         FDateTime:TDateTime;
       public
         constructor create;
       published
         [ADateTime]
         property Day:TDateTime read FDateTime write FDateTime;
       end;
     
       constructor ADateTimeAttribute.Create(const aArg: TDateTime);
       begin
         FArg := aArg;
       end;
     
       constructor ADateTimeAttribute.Create(const aArg: string );
       var
         MySettings:TFormatSettings;
       begin
         { 根据编译器如何格式化%DATE%包含来设置日期格式 }
         MySettings := DefaultFormatSettings;
         MySettings.ShortDateFormat:='yyyymmdd';
         MySettings.DateSeparator :='/';
         { 现在进行转换 }
         FArg := StrToDateTime(aArg, MySettings);
       end;
       
       { 我们查询rtti来设置值 }
       constructor TMyDateTimeClass.Create;
       var
        Context : TRttiContext;
        AType : TRttiType;
        Attribute : TCustomAttribute;
       begin
         inherited;  
         Context := TRttiContext.Create;
         try
           AType := Context.GetType(TypeInfo(TMyDateTimeClass));
           for Attribute in  AType.GetAttributes do begin
             if Attribute is ADateTimeAttribute then
               FDateTime := ADateTimeAttribute(Attribute).Date;
           end;
         finally
           Context.Free
         end;    
       end;
     
    var
     Test:TMyDateTimeClass;      
    begin
       Test := TMyDateTimeClass.Create;
       try
         writeln('Compile date is :',DateTimeToStr(Test.Day));
      finally
        Test.Free;
      end;
    end.

From the Free Pascal wiki, page "Custom Attributes/zh CN", as the Internet Archive kept it on 2025-05-16, last changed on 2024-07-13. 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.

Revision 3, last changed by WikiArchive .

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.