Custom Attributes/zh CN
│ English (en) │ русский (ru) │ 中文(中国大陆) (zh_CN) │
自定义属性(Custom Attributes)目前允许您为类型定义和类的已发布属性添加额外的元数据,这些元数据可以通过RTTI(运行时类型信息)进行查询。
截至2020年10月,该功能仅在FPC主干版本(即3.3.1)中可用
属性可以用来做什么?
属性用于将特定的元数据与类相关联。例如,您可以使用属性来标记一个类,指定其对应的数据库表名,或者为Web服务类添加一个标识其基本路径的字符串。
如何声明属性?
属性只是从新的系统类型`TCustomAttribute`派生的类。实现此类`TCustomAttribute`后代的构造函数是最重要的特性,因为它们用于向属性传递额外的参数(如上述示例中的关联数据库表名或基本路径)。
重要:如果您想在不使用任何参数的情况下使用您的属性类型,则必须自己声明一个无参数的构造函数,因为`TCustomAttribute`的构造函数是私有的。
如何使用属性?
通过将至少一个属性子句放在类型或属性之前,可以将属性绑定到类型或属性。对于类型,属性在类型定义(如类、记录或枚举声明)或唯一类型重声明(例如"TLongInt = type LongInt")中指定。仅允许类型重声明,不允许仅重命名类型(例如"TLongInt = LongInt")。
如果设置了新的模式开关`PREFIXEDATTRIBUTES`(在Delphi和DelphiUnicode模式下是默认的),则可用属性子句。
属性子句的语法如下:
ATTRIBUTECLAUSE::='[' ATTRIBUTELIST ']'
ATTRIBUTELIST::=ATTRIBUTE [, ATTRIBUTELIST ]
ATTRIBUTE::=IDENTIFIER [ ( PARAMLIST ) ]
PARAMLIST::=CONSTEXPR [, PARAMLIST ]
其中,`IDENTIFIER`是属性类的名称。如果属性类的名称以"Attribute"结尾(不区分大小写),则后续可以省略"Attribute"后缀。因此,在以下示例中,`TMyAttribute`和`TMy`是等效的替代方案:
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单元,访问属性的方式如下:
对于类型:
- 使用`TTypeData`中的`AttributesTable`字段、
- 对`PTypeInfo`使用`GetAttributeTable`、
- 对属性表使用`GetAttribute`结合索引获取`TCustomAttribute`实例。
对于属性:
- 使用`TPropInfo`的`AttributesTable`、
- 对属性表使用`GetAttribute`结合索引获取`TCustomAttribute`实例、
- 对`PPropInfo`使用`GetPropAttribute`结合索引获取`TCustomAttribute`实例。
对于Rtti单元,访问属性的方式如下:
对于类型:
- 对目标类型的`TRttiType`使用`GetAttributes`。
对于属性:
- 对目标属性的`TRttiProperty`使用`GetAttributes`。
属性特性与Delphi的兼容性
该特性本身与Delphi兼容,但FPC对未绑定属性的容忍度较低。如果属性类未知或属性子句未绑定到有效的类型或属性,编译器将生成错误。
FPC的RTTI不被认为是与Delphi兼容的,但它涵盖了相同的功能。与Delphi(使用Invoke创建属性实例)不同,FPC使用构造函数函数。FPC的实现优势在于它可以在没有完整Invoke支持的系统上工作。
此外,使用`PREFIXEDATTRIBUTES`模式开关会禁用函数、方法和过程/方法类型的指令子句。启用`PREFIXEDATTRIBUTES`模式开关后,以下用法不再允许:
procedure Test; [cdecl];
begin
end;
完整示例
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.