丰富的元数据主要是为了更精确地保留和传达程序员的意图。例如,程序员经常会针对一个命名的值定义一对方法,典型的情形就是用一个方法get(获取)这个值,另个方法set(设定)这个值。一个CLR类型可以包含附加的元数据,来表明该类型中哪些方法是以这种方式使用的。这种附加元数据被正式命名为属性(property)。
CLR属性是类型的成员,它指定了一个或两个方法,并与属于该类型的命名值相对应。与字段一样,属性有名字和类型。与字段不同的是,属性没有存储空间。准确地说,属性只是一个指向同一类型中其他方法的命名引用,
类型的属性可以通过System.Type.CetProperties和System.Type.GetProperty方法访问,这两个力法都返回描述属性的System..Reflection.Propertylnfo。你可以分别通过PropertyInfo.Name成员和PropertyInfo.PropertyType成员确定属性的名称和类型。更有趣的成员是GetGetMethod方法和GetSetMethod方法。如图4.5所示,这些方法都会返回描述属性的getter(get访问器)方法和setter(set访问嚣)方法的MethodInfo.这两个方法能接收一个布尔值用于控制是否返回非公有方法。由于getter和setter都足可选的,因而这两个方法也会返回null.
为了减少那些含有属性的类型的开放区(surface area),对应于属性的方法往往用specialname元数据特性进行标记,其作用是通知编详器和工具从常规的用法中隐藏属性的个体方法。换而言之,许多编程语言允许使用与访问字段相同的语法读写属性。
下面的C#代码使用类型Invoice,它有一个System.Decimal类型的名为Balance的属性:
public sealed cless Utils{
public static void Adjust(Invoice inv) {
decimal amouet=inv.Balance; // 调用"getter"方法
amount *= 1.0829;
inv.Balance = amount; //调用 "settrer"方法
}
}
尽管语法上与字段相似,但这段代码将触发与Balarce属性相对应的getter和setter方法的调用。当试图通过传引用的方式将Balance传递绐另一个方法时,它不是一个字段的事实就变得更加明显了。
puLlic sealed Class Utils {
public void TaxIt(ref decimal amount){
amount = amount * 1.0825;
public Static void Adjust(Invoice inv){
//非法——inv.Balance没有存储空间!
TaxIt(ref inv Balance);
}
根据编程语言语法创建的构件,有时会产生模糊的语义,这恰好就是个例子.
淡起模糊语义的问题,事实上,每种编程语言都创建了各自的语法,用于将属性定义为类型的成员。在C#中,它的语法是字段和方法声明语法的混合物。考察下面的C#类型声明:
注意,在这个属性定义中,两个方法都被声明了,C#编译器将基于属性的名称和类型来推断出方法的名称和签名,如下所示:
public decimal get_Balance()
public void set_Balance (decimal value)
生成的类型还包含一些附加元数据,这些元数据用来将这两个方法绑定到一起作为Balance属性,其类型是System.Decimal。到目前为止达成的共识是:每一个方法都会被标记为speclalname,它们不会在VisualStudio的智能感知中出现。
与属性的思路一样,对于用作注册或取消事件处理程序( event handler)的指定方法.CLR提供了显式的支持。CLR事件【CLR event)是类型的命名成员,它引用同一类型中的其他方法。在被引用的这些方法中,有一个是用于注册事件处理程序的。另一个方法则用于取消该注册.一个给定的事件可以有多重事件处理程序,这些处理程序有着截然不同的名字。就像属性一样,事件属于一个类型。事件的类型必须派生于System.Delegate,在第6章中将对此进行详细的说明。b