元数据和反射
大多数程序都要处理数据,包括读、写、操作和显示数据。(图形也是一种数据的形式。)然而,对于某些程序来说,它们操作的数据不是数字、文本或图形,而是程序和程序类型本身的信息
- 有关程序及其类型的数据被称为元数据(metadata),它们保存在程序的程序集中
- 程序在运行时,可以查看其他程序集或其本身的元数据。一个运行的程序查看本身的元数据或其他程序的元数据的行为叫做反射(reflection)
对象浏览器是显示元数据的程序的一个示例。它可以读取程序集,然后显示所包含的类型以及类型的所有特性和成员
Type类
BCL声明了一个叫做Type的抽象类,它被设计用来包含类型的特性。使用这个类的对象能让我们获取程序使用的类型的信息
由于Type是抽象类,因此它不能有实例。而是在运行时,CLR创建从Type(RuntimeType)派生的类的实例,Type包含了类型信息。当我们要访问这些实例时,CLR不会返回派生类的引用而是Type基类的引用。但是,为了简单起见,在本章剩余的篇幅中,我会把引用所指向的对象称为Type类型的对象(虽然从技术角度来说是一个BCL内部的派生类型的对象)
- 需要了解的有关Type的重要事项如下:
- 对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象
- 程序中用到的每一个类型都会关联到独立的Type类的对象
- 不管创建的类型有多少个实例,只有一个Type对象会关联到所有这些实例
获取Type对象
必须命名空间:using System.Reflection
Type t = myInstance.GetType();
什么是特性
特性(attribute)是一种允许我们向程序的程序集增加元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类
- 将应用了特性的程序结构(program construct)叫做目标(target )
- 设计用来获取和使用元数据的程序(比如对象浏览器)叫做特性的消费者(consumer)
- .NET预定了很多特性,我们也可以声明自定义特性
- 我们在源代码中将特性应用于程序结构
- 编译器获取源代码并且从特性产生元数据,然后把元数据放到程序集中
- 消费者程序可以获取特性的元数据以及程序中其他组件的元数据。注意,编译器同时生产和消费特性
据惯例,特性名使用Pascal命名法并且以Attribute后级结尾
应用特性
特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集。我们可以通过把特性应用到结构来实现
- 在结构前放置特性片段来应用特性
- 特性片段被方括号包围,其中是特性名和特性的参数列表
特性的应用
[ Serializable ] // 特性
public class MyClass
{
...
}
[ MyAttribute("Simple class","Version 3.57") ] // 带有参数的特性
public class MyOtherClass
{
...
}
注意
- 大多数特性只针对直接跟随在一个或多个特性片段后的结构
- 应用了特性的结构称为被特性装饰(decorated 或 adorned,两者都应用得很普遍)
预定义的保留的特性
- Obsolete 特性
- Conditional 特性
- 调用者信息 特性
- DebuggerStepThrough 特性
定义在.NET中的重要特性
特 性 | 意 义 |
---|---|
CLSCompliant | 声明可公开的成员应该被编译器检测是否符合CLS。兼容的程序集可以被任何NET兼容的语言使用 |
Serializable | 声明结构可以被序列化 |
NonSerialized | 声明结构不能被序列化 |
DLLImport | 声明是非托管代码实现的 |
NebMethod | 声明方法应该被作为XML Web服务的一部分暴露 |
AttributeUsage | 声明特性能应用到什么类型的程序结构。将这个特性应用到特性声明上 |
有关应用特性的更多内容
- 多个特性可以使用下面列出的任何一种格式:
- 独立的特性片段相互叠在一起
- 单个特性片段,特性之间使用逗号分隔
- 我们可以以任何次序列出特性
示例
// 多层结构
[ Serializable ]
[ MyAttribute("Simple class", "Version 3.57")]
// 逗号分隔
[ MyAttribute("Simple class", "Version 3.57"), Serializable]
自定义特性
特性是特殊类型的类
- 用户自定义的特性类叫做自定义特性
- 所有特性类都派生自
System.Attribute
总体来说,声明一个特性类和声明其他类一样。然而,有一些事项值得注意,如下所示:
- 要声明一个自定义特性,需要做如下工作
- 声明一个派生自System.Attribute的类
- 给它起一个以后缀
Attribute
结尾的名字
- 安全起见,通常建议你声明一个
sealed
的特性类
// 特性名 后缀 基类
public sealed class MyAttributeAttribute : System.Attribute
{
...
}
由于特性持有目标的信息,所有特性类的公共成员只能是:
- 字段
- 属性
- 构造函数
访问特性
我们可以使用Type对象的IsDefined方法来检测某个特性是否应用的某个类上
使用GetCustomAttributes
方法
- GetCustomAttributes方法返回应用到结构的特性的数组
- 实际返回的对象是
object
的数组,因此我们必须将它强制转换为相应的特性类型 - 布尔参数指定是否搜索继承树来查找特性
object[] AttArI = t.GetCustomAttributes(false);
- 实际返回的对象是
- 调用
GetCustomAttributes
方法后,每一个与目标相关联的特性的实例就会被创建