有关程序集其类型的数据被称为 元数据,它们保存在程序的程序集中。
一个运行的程序查看本身的元数据或其他程序的元数据的行为叫做 反射。
Type
类每一个类型都有自己的成员和特性,BCL声明了一个 Type
抽象类,它用来包含类型的特性,即使用 Type
类可以反射数据。
对于程序中用到的每个类型,CLR都会创建一个包含这个类型信息的 Type
类型的对象(在技术角度来讲是一个BCL内部的Type
派生类型的对象),即每个类型都会关联到独立的 Type
类的对象,无论该类型创建了多少个实例,都只会有一个 Type
对象与之关联。
在一个 Type
对象中可以获取需要了解的有关类型几乎所有的信息,以下列出比较有用的 Type
类成员:
Name
:属性,返回类型的名字Namespace
:属性,返回命名空间Assembly
:属性,返回声明类型的程序集GetFields
:方法,返回类型的字段列表GetProperties
:方法,返回类型的属性列表GetMethods
:方法,返回类型的方法列表Type
对象在使用之前,要使用
System.Reflection
命名空间
使用 GetType
方法 和 Typeof
运算符 可以获取 Type
对象。
对应实例,使用GetType
方法获取 Type
对象:
Type T = myInstance.GetType();
对应类型 使用 typeof
运算符获取 Type
对象:
Type t = typeof(DerivedClass);
特性是一种允许我们向程序的程序集增加元数据的语言结构,它是用于保存程序结构信息的某种特殊类型的类。
编译器获取源代码并从特性中产生元数据,然后把元数据放到程序集中;
消费者程序 可以获取特性的元数据以及程序中其他组件的元数据,即编译器同时生产和消费特性
特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集
个人感觉特性可以相对于预处理指令,区别在于预处理指令作用是指明编译器如何生成程序集,而特性则是作用在程序集中。
使用前置 特性片段 来应用特性,特性片段被方括号包围,方括号中为 特性名 和 特性的参数列表
我们可以使用 assembly:
和 module:
来设定全局特性,即程序集或块级别,程序集级别的特性一般都要放置在如何命名空间之外,通常放置在 AssemblyInfo.cs
文件中
Obsolete
特性在编写程序时,很多时候需要编写新方法来替代旧方法,这时可以使用 Obsolete
特性将程序结构标注为过期。
例如:
class Program
{
[Obsolete("Use method SuperPrintOut")]
static void PrintOut(string str)
{
Console.WriteLine(str);
}
static void Main(string[] args)
{
PrintOut("Start of Main");
}
}
Obsolete
特性还有一个重载接受一个布尔值作为第二个参数,这个参数指定目标是否被标记为 错误 而不仅仅是 警告
Conditional
特性Conditional
特性以编译符号作为参数来使用,如果编译符号已经定义了,那么编译器会调用该特性所包含的代码就和普通方法没有区别,否则编译器会忽略代码中这个方法的调用。
这与 #if
预处理指令相比,不同的是定义方法的CIL代码还是会在程序集中,只是在调用代码的时候会被忽略。
调用者信息特性可以访问很多关于调用者源代码的信息,如:
CallerFilePath
:访问文件路径CallerLineNumber
:代码行数CallerMemberName
:调用成员的名称注意这些特性只能用于方法中的可选参数,例如:
using System;
using System.Runtime.CompilerServices;
public static class Program
{
public static void MyTrace(
string message,
[CallerFilePath] string fileName = '',
[CallerLineNumber] int lineNumber = 0,
[CallerMemberName] string MemberName = ''
)
{
Console.WriteLine("File: {0}", fileName); // 文件路径 + 文件名
Console.WriteLine("lineNumber: {0}", lineNumber); // 19
Console.WriteLine("Call from: {0}", MemberName); //Main
Console.WriteLine("Msg: {0}", message); //Simple Message
}
public static void Main()
{
MyTrace("Simple Message");
}
}
DebuggerStepThrough
特性在单步调试代码时,常常希望调用器不要进入某些方法,而 DebuggeStepThrough
特性告诉调试器在执行目标代码时不要进入该方式调试。使用 DebuggeStepThrough
需要注意的是:
System.Diagnostics
命名空间用户自定义的特性叫做自定义特性,所有特性类都派生自 System.Attribute
.特性类应该表示目标结构的一些状态
声明一个特性类与声明一个普通类几乎一样,只是要注意:
System.Attribute
类Attribute
结尾(而当目标应用特性时,可以选择省略后缀,省略后缀之后叫做 短名称)sealed
关键字可以为类添加特性,而特性本身又是类,所以可以有一个很重要的预定义特性用作于自定义特性上,而AttributeUsage
特性也就是一个预定义特性,我们可以使用它来限制特性使用在某个目标类型上。
即 AttributeUsage
特性作用于自定义特性类,其中它的公共属性有三个:
validOn
:保存目标类型,可接受目标类型是 AttributeTarget
枚举的成员,可以使用 按位 或运算符来组合使用Inherited
:布尔值,默认值为 ture
,表示特性是否能被继承AllowMutiple
:布尔值,默认值为 false
,表示目标是否能被应用多个特性例如:
[AttributeUsage(AttributeTarget.Method | AttributeTarget.Constructor,
Inherited = false,
AllowMutiple = false)]
public sealed class MyAttributeAttribute: System.Attribute
{
....
}
使用 Type
实例的 IsDefined
和 GetCustomAttributes
方法可以访问特性
IsDefined
方法使用 IsDefined
方法可以查询特性是否添加到某个类上,它接受两个参数:
Type
对象eg:
Type t = mc.GetType();
bool isDefined = t.IsDefined(tyoeof(MyAttribute), false);
GetCustomAttributes
方法使用 GetCustomAttributes
方法可以输出应用到的特性数组,接受一个布尔值参数,用于表示是否搜索它的继承树来查找特性。此时输出的都是 object
类型,所以必须使用类如 as
的转换符,强制转换相应的特性类型。
eg:
object[] Attr = t.GetCustomAttributes(false);