Attribute 和 RTTI (Berlin帮助翻译整理)
注:基于本人英文水平,以下翻译只是我自己的理解,如对读者造成未知影响,一切后果自负。
介绍attribute的基本概念,一般使用情况,和一些在Delphi中的限制。
注:Delphi attribute在C++Builder中不支持。 Attribute一般翻译为“属性”,这里保留原样。
介绍
Attribute 是Delphi语言的特性,允许你注释(标注)类型和类型成员一些特定的额外信息. 这个信息可以在运行时查询.
Attribute 和 RTTI
Attribute 不会修改类型和类型成员的行为. 能够在编译的二进制文件中附加Attribute, 首先你需要有RTTI信息,意思是你不能明确的禁用RTTI信息. 比如,下面的代码, SomeCustomAttribute 将不会编译到二进制文件中, 因为在类TderivedObject 上明确禁用了RTTI 信息.
Type
//编译指令,禁用类的所有RTTI信息
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
TDerivedObject = class(TObject)
[SomeCustomAttribute()] //这个不会编译到二进制文件中
procedure Shoot;
end;
声明Attribute (RTTI)
介绍创建attributes的基本方法, attribute 类的适当设计, 和一般使用情况.
声明一个Attribute
一个attribute 是一个简单的class type. 定义自己的attribute, 你必须从类: System.TCustomAttribute中继承:
type
MyCustomAttribute = class(TCustomAttribute)
end;
现在MyCustomAttribute 可以标注任何类型和类型成员(比如class, record, 或 interface):
type
[MyCustomAttribute] //标注类型
TSpecialInteger = type integer;
TSomeClass = class
[MyCustomAttribute] //标注类的成员
procedure Work;
end;
请注意,声明的attribute类不能被声明为抽象类,不应该包含任何抽象方法. 即使可以编译通过, 但编译生成的二进制文件不包含attribute信息.
Attribute的名称中,以 “Attribute” 结束的被隐式缩短
假如你声明了2个相同前缀的 TCustomAttribute 的子类, 有一个有’Attribute’ 后缀, 像这样:
- MyCustom
- MyCustomAttribute
有后缀 ‘Attribute’ 的类 (MyCustomAttribute) 一直可用, 短名称的类 (MyCustom) 变成不可用(因为前一个已经被隐式的缩短了,造成同名).
以下代码演示这个问题.
type //测试模棱两可的名字 TestAttribute = class(TCustomAttribute) end; // 变成不可访问 Test = class(TCustomAttribute) end; [Test] // 运行时是TestAttribute TAmbigiousClass = class end;
Attribute中的构造方法
通常,一个attribute 被设计成携带一些附加的信息用于运行时查询. Attribute类允许指定一些自定义的信息, 可以定义构造方法constructor :
type
AttributeWithConstructor = class(TCustomAttribute)
public
constructor Create(const ASomeText: String);
end;
然后可以这样使用:
type
[AttributeWithConstructor(‘Added text value!’)]
TRecord = record
FField: Integer;
end;
注释(标注) 类型和类型成员
这个主题描述通过attribute标注类型和类型成员的语法和规则.
一般语法
标注一个 Delphi 类型或成员, 例如一个class 或 class 成员, 必须放在类型或类型成员的声明之前:
[CustomAttribute]
TMyClass = class;
如果attribute 类的名字是以”Attribute”结尾的,你可以省略 “Attribute” 后缀:
[Custom]
procedure DoSomething;
在attributes的名字后加一组括号也是一个有效的语法:
[Custom()]
TMyRecord = record;
一些attribute 接受参数. 要传递参数, 使用像调用方法一样的语法:
[Custom(Argument1, Argument2, …)]
TSimpleType = set of (stOne, stTwo, stThree);
多个attribute 标注一个类型时, 你可以使用上面任何其中一种语法:
[Custom1]
[Custom2(MyArgument)]
FString: String;
或者使用逗号分隔:
[Custom1, Custom2(MyArgument)]
function IsReady: Boolean;
Attribute 的参数只能是常量表达式
一个attribute 标注类型或成员时,被标注的类型或成员的RTTI信息中会包含attribute相关的信息,这些信息包含:
- Attribute 的类型.
- Constructor方法的指针.
- 能过构造方法传递的常量列表.
通过constructor方法传递的参数必须是常量表达式.因为这些值会编译到二进制文件:
- 你可以使用 constant expressions, 包括 sets, strings, 和 ordinal expressions.
- 可以使用 TypeInfo() 传递类型信息,因为 RTTI 块的地址编译时是已知的.
- 可以使用 class references 因为类的引用地址编译时是已知的.
- 不能使用 out 或 var 参数,因为他们运行时需要计算参数地址.
- 不能使用 Addr() 或 @.
下面的代码演示了不合法的Attribute标注使用方法:
var
a, b: Integer;
type
[SomeAttribute(a + b)]
TSomeType = record
// …
end;
以下代码是正确的:
const
a = 10;
b = 20;
type
[SomeAttribute(a + b)]
TSomeType = record
// …
end;
编译时a和b的值都是明确的; 因此, 可以直接计算这个表达式.
在运行时获取 Attributes
提供怎样获取attributes的信息.
Attribute 实例
查询时才会产生真实的Attribute实例. 意思是Attribute实例不会自动创建.
参考以下 attribute 定义:
type TSpecialAttribute = class(TCustomAttribute) public FValue: String; constructor Create(const AValue: String); end; constructor TSpecialAttribute.Create(const AValue: String); begin FValue := AValue; end;
TSpecialAttribute 用来标注:
type [TSpecialAttribute(‘Hello World!’)]
TSomeType = record
…
end;
从TSomeType 类型获取attribute, 必须通过 System.Rtti 单元的相关函数. 下面是示例代码:
var LContext: TRttiContext; LType: TRttiType; LAttr: TCustomAttribute; begin { Create a new Rtti context } LContext := TRttiContext.Create { 从TSomeType 类型中抽取attribute信息 } LType := LContext.GetType(TypeInfo(TSomeType)); { Search for the custom attribute and do some custom processing } for LAttr in LType.GetAttributes() do if LAttr is TSpecialAttribute then Writeln(TSpecialAttribute(LAttr).FValue); { Destroy the context } LContext.Free; end;
如上面代码所示, 用户必须写相关代码. 真实的 attribute实例被创建在TRttiType.GetAttributes 方法. 注意例子中没有释放attribute实例; TRttiContext 会释放所有资源.
异常
因为attribute的实例是能过用户写代码创建的, 这就有可能在创建时产生异常. 一般推荐使用 try .. except 语句块来查询 attribute.
一个典型的 attribute 构造方法像以下这样:
constructor TSpecialAttribute.Create(const AValue: String); begin if AValue = '' then raise EArgumentException.Create('Expected a non-null string'); FValue := AValue; end; 标注TSomeType 时传递了空值: type [TSpecialAttribute('')] TSomeType = record ... end;
在这个情况下, 将产生异常 EArgumentException ,推荐用以下方法:
{ Search for the custom attribute and do some custom processing } try for LAttr in LType.GetAttributes() do if LAttr is TSpecialAttribute then Writeln(TSpecialAttribute(LAttr).FValue); except { ... Do something here ... } end;
编译器提供的 Attribute
Delphi compilers包含了一些特定的Attribute
Ref
Ref attribute 用于修饰函数中的const参数,让参数变成引用传递 (不是值传递),比如:
function CompareStr(const S1, S2: string): Integer;
function FunctionName(const [Ref] parameter1: Class1Name; [Ref] const parameter2: Class2Name);
Unsafe
将一个函数的结果标记为不安全的,使编译器将函数结果视为“Unsafe”,这将禁用对象的ARC管理(ARC management)
注:ARC –> Automatic Reference Counting 引用记数
在函数上使用 unsafe attribute ,使用以下语法:
[Result: Unsafe] function ReturnUnsafe: TObject;
警告: [Unsafe] 还可以应用到variables (members) 和 parameters. 在System单元以外使用,是在非常罕见的情况下的. 它被认为是危险的,它的使用是不建议的,因为没有与引用计数的代码生成.
注意: 你只能通过一个[Unsafe]变量到一个被标记为[Unsafe]的var或 out参数。你不能对标记[Unsafe] 的var 或 out参数传递一个强引用(意思是实参,形参都要标记成[Unsafe])
Volatile
volatile attribute 被用于标记字段, 该字段是受不同的线程更改的, 因此,代码生成时,是不会优化在寄存器或另一个临时存储位置中复制他的值.
你可以使用 volatile attribute 去标记下列声明:
- Variables (global and local)
- Parameters
- record 或 class 的字段
不能使用 volatile attribute 去标记下列声明:
- Type
- Procedures, Functions or Methods
- Expressions
type
TMyClass = class
private
[volatile] FMyVariable: TMyType; //类中的对象类型也不可以
end;
Weak
weak attribute 标记声明为弱引用(用在类之间的相互引用时,可参考Berlin版中的TownedCollection 类定义)。