[翻译] Attribute 和 RTTI

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] varout参数传递一个强引用(意思是实参,形参都要标记成[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 类定义)。

分享到: