元数据(metadata):有关程序及其类型的数据,保存在程序集中。
反射(reflection):一个运行的程序查看自身或其他程序的元数据。需要使用System.Reflection
命名空间。
BCL中声明了一个叫做Type
的抽象类,包含了类型的特性。使用这个类的对象能获取程序使用的类型的信息。Type是抽象类,实际上访问的是CLR创建的Type(RuntimeType)的派生类。
程序中的每一个类型,都会关联到独立的、由CLR创建的包含这个类型信息的Type类对象。类型的多个实例只关联到一个Type类对象。
class MyClass{...}
void static Main{
MyClass mc = new();
Console.WriteLine(mc.GetType()); //返回mc的Type对象
Console.WriteLine(typeof(MyClass)); //返回MyClass的Type对象,不能使用typeof(mc)
特性允许我们向程序的程序集添加元数据。它是用于保存程序结构信息的类。
特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集。
所有特性类都派生自System.Attribute
。
几个相关术语:
■ 目标:应用了特性的程序结构
■ 消费者:用来获取元数据的程序(如对象浏览器),称为特性的消费者
在源码中将特性应用于程序结构,编译器获取源码,并从特性产生元数据,然后把元数据放到程序集中。
消费者程序可以获取特性的元数据,及程序组其他组件的元数据。
编译器同时生产和消费特性。
自定义特性名以Attribute结尾,但是应用特性时不需要写Attribute。
方式:在结构前放置由方括号括起的特性片段。
应用一个特性是一条声明语句,不会决定在什么时候构造特性类的对象。
大多数特性只针对直接跟随在其后的结构。
多个特性可以同时使用。
可以显示地标注特性,从而将它应用到特殊的目标结构。
特性目标包含:
event | field |
method | param |
property | return |
type | typevar |
assembly | module |
程序集级别(assembly)的特性必须放在任何命名空间之外,且通常放在AssemblyInfo.cs文件中,该文件通常包含有关公司、产品及版权信息的元数据。
[Serializable] //特性片段
public MyClass{...}
[MyAttribute("simple")] //带参数的特性片段
public AnotherClass{...}
[Serializable, MyAttribute] //多个特性可在一个方括号内使用逗号隔开
[My2] //也可分行写
public ThirdClass{...}
[method: MyAttr("msg")]
[return: MyAttr("ret msg")] //显示指定特性应用于返回值
public void FuncA(){...}
使用Obsolete特性将程序结构标注为过期的,并在代码编译时显示警告信息,或直接在使用时产生错误(不能通过编译)。位于System
命名空间内。
[Obsolete("warning")] //设置特性Obsolete,并设置警告信息
void FuncA(){...}
[Obsolete("error", true)] // 第二个true标记了调用FuncB为错误而不是警告
void FuncB(){...}
// Main内
FuncA(); //正常运行,编译发出警告
FuncB(); //编译不通过,发生错误
用于限定方法的调用,当使用Condition特性并传入一个编译符号参数时,若该编译符号被#define定义,则被修饰的结构能正常调用,若编译符号没有被定义,则编译器会忽略所有这个被修饰结构的调用(定义方法的CIL代码本身仍会在程序集中)。
#define DoTrace //定义编译符号 DoTrace
using System.Diagnostics; //Conditional所在命名空间
namespace Program
{
class MyClass
{
[Conditional("DoTrace")] //当DoTrace被定义时,FuncA可正常调用
static void FuncA(){...}
[Conditional("DoSth")] //当DoSth被定义时,FuncB可正常调用
static void FuncB(){...}
static void Main()
{
FuncA(); //DoTrace已定义,编译器正常调用
FuncB(); //DoSth未定义,编译器跳过调用
}
}
}
可以访问源代码信息,只能用于方法中的可选参数:
使用这些特性的可选参数,若调用方法时没有显示指定了参数,则系统会提供源代码的文件路径,调用该方法的代码行数,和调用该方法的成员名称。
using System.Runtime.CompilerServices; //使用所需的命名空间
namespace Program
{
static void FuncA([CallerLineNumber]int line = 0,
[CallerMemberName]string name = "")
{
System.Console.Write("line = {0}, name = {1}", line, name);
}
static void Main()
{
FuncA();
}
}
//输出
line = 12, name = Main
设置代码不进入调试。位于System.Diagnostics
内。
可以用于类、结构体、构造函数、方法、访问器。
限制特性使用在某个目标类型上,只能应用于自定义特性类。
[AttributeUsage(AttributeTarget.Method)] //限制FuncAttribute只针对方法
class FuncAttribute: System.Attribute{...}
三个重要的属性:
|
同时使用多个值。自定义特性类声明基类为System.Attribute,且特性名以Attribute结尾。
自定义特性的公共成员只能是:字段、属性、构造函数。
为了更安全,推荐将特性类声明为sealed
。
推荐在特些声明中使用AttributeUsage显示指定目标组。
class MyAttribute: System.Attribute
{
public MyAttribute(string s) //构造函数也需要写Attribute
{
...
}
}
使用Type对象的IsDefined和GetCustomAttributes方法。
用来检测某个特性是否应用到了某个类上。
接受两个参数:
■ 第一个参数指定特性的Type类对象。
■ 第二个参数指示是否搜索参数一的继承树来查找这个特性。
[MyAttribute]
class MyClass{...}
// main内
MyClass mc = new();
Type t = mc.GetType();
bool isDefined = t.IsDefined(typeof(MyAttribute), false); //判断MyCLass是否被MyAttribute修饰
返回应用到结构的特性的数组。实际返回object类型的数组。
接受一个参数,指定是否沿继承树搜索。