Sign in Join
Wiki copytemporary

Custom Attributes/zh CN: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Custom Attributes/zh CN

OldNewLine
1+自定义属性(Custom Attributes)目前允许您为类型定义和类的已发布属性添加额外的元数据,这些元数据可以通过RTTI(运行时类型信息)进行查询。
2+
3+**截至2020年10月,该功能仅在FPC主干版本(即3.3.1)中可用**
4+
5+## Contents
6+
7+ * 1 属性可以用来做什么?
8+ * 2 如何声明属性?
9+ * 3 如何使用属性?
10+ * 3.1 查询属性
11+ * 4 属性特性与Delphi的兼容性
12+ * 5 完整示例
13+
14+## 属性可以用来做什么?
15+
16+属性用于将特定的元数据与类相关联。例如,您可以使用属性来标记一个类,指定其对应的数据库表名,或者为Web服务类添加一个标识其基本路径的字符串。
17+
18+## 如何声明属性?
19+
20+属性只是从新的系统类型`TCustomAttribute`派生的类。实现此类`TCustomAttribute`后代的构造函数是最重要的特性,因为它们用于向属性传递额外的参数(如上述示例中的关联数据库表名或基本路径)。
21+
22+**重要** :如果您想在不使用任何参数的情况下使用您的属性类型,则必须自己声明一个无参数的构造函数,因为`TCustomAttribute`的构造函数是私有的。
23+
24+## 如何使用属性?
25+
26+通过将至少一个属性子句放在类型或属性之前,可以将属性绑定到类型或属性。对于类型,属性在类型定义(如类、记录或枚举声明)或唯一类型重声明(例如"TLongInt = type LongInt")中指定。仅允许类型重声明,不允许仅重命名类型(例如"TLongInt = LongInt")。
27+
28+如果设置了新的模式开关`PREFIXEDATTRIBUTES`(在Delphi和DelphiUnicode模式下是默认的),则可用属性子句。
29+
30+属性子句的语法如下:
31+```
32+ATTRIBUTECLAUSE::='[' ATTRIBUTELIST ']'
33+ ATTRIBUTELIST::=ATTRIBUTE [, ATTRIBUTELIST ]
34+ ATTRIBUTE::=IDENTIFIER [ ( PARAMLIST ) ]
35+ PARAMLIST::=CONSTEXPR [, PARAMLIST ]
36+```
37+
38+其中,`IDENTIFIER`是属性类的名称。如果属性类的名称以"Attribute"结尾(不区分大小写),则后续可以省略"Attribute"后缀。因此,在以下示例中,`TMyAttribute`和`TMy`是等效的替代方案:
39+```
40+program tcustomattr;
41+
42+ {$mode objfpc}{$H+}$
43+ {$modeswitch prefixedattributes}
44+
45+ type
46+ TMyAttribute = class(TCustomAttribute)
47+ constructor Create;
48+ constructor Create(aArg: String);
49+ constructor Create(aArg: TGUID);
50+ constructor Create(aArg: LongInt);
51+ end;
52+
53+ {$M+}
54+ [TMyAttribute]
55+ TTestClass = class
56+ private
57+ fTest: LongInt;
58+ published
59+ [TMyAttribute('Test')]
60+ property Test: LongInt read fTest;
61+ end;
62+ {$M-}
63+
64+ [TMyAttribute(1234)]
65+ [TMy('Hello World')]
66+ TTestEnum = (
67+ teOne,
68+ teTwo
69+ );
70+
71+ [TMyAttribute(IInterface), TMy(42)]
72+ TLongInt = type LongInt;
73+
74+ constructor TMyAttribute.Create;
75+ begin
76+ end;
77+
78+ // ... 其他构造函数实现 ...
79+
80+ constructor TMyAttribute.Create(aArg: String);
81+ begin
82+ end;
83+
84+ constructor TMyAttribute.Create(aArg: LongInt);
85+ begin
86+ end;
87+
88+ constructor TMyAttribute.Create(aArg: TGUID);
89+ begin
90+ end;
91+
92+ begin
93+
94+
95+ begin
96+
97+ end.
98+```
99+
100+### 查询属性
101+
102+属性可以通过TypInfo和Rtti单元进行访问。
103+
104+对于TypInfo单元,访问属性的方式如下:
105+
106+对于类型:
107+
108+ * 使用`TTypeData`中的`AttributesTable`字段、
109+ * 对`PTypeInfo`使用`GetAttributeTable`、
110+ * 对属性表使用`GetAttribute`结合索引获取`TCustomAttribute`实例。
111+
112+对于属性:
113+
114+ * 使用`TPropInfo`的`AttributesTable`、
115+ * 对属性表使用`GetAttribute`结合索引获取`TCustomAttribute`实例、
116+ * 对`PPropInfo`使用`GetPropAttribute`结合索引获取`TCustomAttribute`实例。
117+
118+对于Rtti单元,访问属性的方式如下:
119+
120+对于类型:
121+
122+ * 对目标类型的`TRttiType`使用`GetAttributes`。
123+
124+对于属性:
125+
126+ * 对目标属性的`TRttiProperty`使用`GetAttributes`。
127+
128+## 属性特性与Delphi的兼容性
129+
130+该特性本身与Delphi兼容,但FPC对未绑定属性的容忍度较低。如果属性类未知或属性子句未绑定到有效的类型或属性,编译器将生成错误。
131+
132+FPC的RTTI不被认为是与Delphi兼容的,但它涵盖了相同的功能。与Delphi(使用Invoke创建属性实例)不同,FPC使用构造函数函数。FPC的实现优势在于它可以在没有完整Invoke支持的系统上工作。
133+
134+此外,使用`PREFIXEDATTRIBUTES`模式开关会禁用函数、方法和过程/方法类型的指令子句。启用`PREFIXEDATTRIBUTES`模式开关后,以下用法不再允许:
135+```
136+procedure Test; [cdecl];
137+ begin
138+
139+ end;
140+```
141+
142+## 完整示例
143+```
144+program testattributes;
145+ {
146+ 这是一个简单的示例,说明如何使用自定义属性。
147+ 该类使用自定义属性在程序启动时检索静态日期。这仅用于演示。
148+ 此演示包含:
149+ - 创建
150+ - 装饰
151+ - 检索
152+ }
153+
154+ {$mode delphi}{$H+}{$M+}
155+ {$warn 5079 off} { 关闭实验性警告 }
156+ uses
157+ sysutils, typinfo, rtti, classes;
158+
159+ type
160+ { 用于装饰类以指定特定日期的自定义属性 }
161+ ADateTimeAttribute = class(TCustomAttribute)
162+ private
163+ FArg:TDateTime;
164+ public
165+ { 展示自定义属性可以有多个构造函数 }
166+ constructor Create(const aArg: TDateTime);overload;
167+ { 我们可以使用预定义的%DATE%编译器包含
168+ 在自定义属性的上下文中,我们需要一个
169+ 常量表达式作为默认参数 }
170+ constructor Create(const aArg: String = {$I %DATE%});overload;
171+ published
172+ property Date:TDateTime read FArg;
173+ end;
174+
175+ { 使用我们的自定义属性装饰的日期时间类 }
176+ { 注意:您可以省略'Attribute',编译器会解析它 }
177+ { [ADateTime(21237.0)] 调用第一个构造函数,显示1958年的某个日期 }
178+
179+ { 这将调用第二个构造函数 }
180+ [ADateTime]
181+ TMyDateTimeClass = class
182+ private
183+ FDateTime:TDateTime;
184+ public
185+ constructor create;
186+ published
187+ [ADateTime]
188+ property Day:TDateTime read FDateTime write FDateTime;
189+ end;
190+
191+ constructor ADateTimeAttribute.Create(const aArg: TDateTime);
192+ begin
193+ FArg := aArg;
194+ end;
195+
196+ constructor ADateTimeAttribute.Create(const aArg: string );
197+ var
198+ MySettings:TFormatSettings;
199+ begin
200+ { 根据编译器如何格式化%DATE%包含来设置日期格式 }
201+ MySettings := DefaultFormatSettings;
202+ MySettings.ShortDateFormat:='yyyymmdd';
203+ MySettings.DateSeparator :='/';
204+ { 现在进行转换 }
205+ FArg := StrToDateTime(aArg, MySettings);
206+ end;
207+
208+ { 我们查询rtti来设置值 }
209+ constructor TMyDateTimeClass.Create;
210+ var
211+ Context : TRttiContext;
212+ AType : TRttiType;
213+ Attribute : TCustomAttribute;
214+ begin
215+ inherited;
216+ Context := TRttiContext.Create;
217+ try
218+ AType := Context.GetType(TypeInfo(TMyDateTimeClass));
219+ for Attribute in AType.GetAttributes do begin
220+ if Attribute is ADateTimeAttribute then
221+ FDateTime := ADateTimeAttribute(Attribute).Date;
222+ end;
223+ finally
224+ Context.Free
225+ end;
226+ end;
227+
228+ var
229+ Test:TMyDateTimeClass;
230+ begin
231+ Test := TMyDateTimeClass.Create;
232+ try
233+ writeln('Compile date is :',DateTimeToStr(Test.Day));
234+ finally
235+ Test.Free;
236+ end;
237+ end.
238+```
239+
240+---
241+*From the Free Pascal wiki, page "Custom Attributes/zh CN", as the Internet Archive kept it on 2025-05-16. 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](/wiki-contact).*

Back to history