维基副本镜像

这是 pascal.chat 维基的镜像。编辑、历史记录和登录都在那里。

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.

版本3,最后由WikiArchive于修改。