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