反射(Reflection)
定义:指对一个程序集中的元数据进行检查的过程。
作用:1、访问程序集中的元数据; 2、使用元数据。如:创建API文档,对象序列化,遍历一个对象的所有属性,使用编译时未知的对象数据填充列表控件。
方式:使用System.Type的实例来访问一个类型的元数据,(instance.GetType(), typeof(ClassName)),它提供了获取类型信息的所有方法。
1、Type.Name
2、Type.IsPublic
3、Type.BaseType
4、Type.GetInterfaces()
5、Type.Assembly
6、Type.GetProperties(), Type.GetMethods(), Type.GetFields()。GetMethods()不能返回扩展方法。
7、Type.GetCustomAttributes()
只要设置了恰当的代码访问安全(code access security, CAS), 反射可以绕过可访问性规则。
泛型类上的反射
C#2.0开始,提供了该能力。
在泛型类上执行运行时反射时,将判断出一个类或者方法是否包含泛型类型,并确定它包含的任何类型参数。
1.判断类型参数的类型。
2.判断类或方法是否支持泛型。
3.为泛型类或者方法获取类型参数。
特性(Attributes)
特性是将额外的数据关联到一个属性以及其他构造的一种方式。它可以说是一种可由用户自定义的修饰符。但是从CIL中可以看出,其实不是修饰付,而是有着独特实力化形式的类。
其他构造包括:类,接口,struct,枚举,委托,事件,方法,构造器,字段,参数,返回值,程序集,类型参数和模块。
对于程序集和模块,我们要加assembly: 和module:前缀,用来限制他们必须出现在using指令之后,并且在其他任何命名空间或类声明之前。
对于返回值,我们要加return:前缀。
其他构造的前缀是可选的。
在使用特性时,语言会自动在名称末尾添加Attribute后缀。在编程时,这个后缀是可选的。而且在应用一个特性时,一般不写。只有在自定义一个特性或者以内联方式使用一个特性时,才会加后缀。如:typeof(DescriptionAttribute)。
特性的作用是添加元数据。会被编译器编译到元数据里面。
自定义特性
特性是对象,所以要定义一个特性,需要定义一个类,并且保证该类从system.Attribute派生。
查找特性简单路径:obj -> obj.GetType().GetProperties() ->get PropertyInfo from step 2's result ->property.GetCustomAttributes(typeof(***Attribute),false)
使用构造器来初始化特性。也可以用具名参数(named parameter )来初始化。具名参数用来设置特定的公共属性和字段。
特性的局限性
向某种构造应用特性的时候,只有字面值(literal values)和类型(tyoeof((int))才能被允许做为参数使用,这是为了确保他们能被序列化到CIL中。
所以不能在使用一个特性的时候调用静态方法。System.DateTime没有字面值。
System.AttributeUsageAttribute
确保对特定的构造进行修饰。这个特性获取一个AttributesTargets的枚举类型标志,同时该特性可以使用具名参数的方式指定是否能在一个构造上进行多次复制。
FlagsAttribute
允许多个枚举值可以组合使用,并且改写枚举的ToString()和Parse()方法。使得ToString()返回各个枚举的名字,以逗号分割。如果不加[Flags],那么返回的值是各个枚举值做和的数值。同样,可以从字符串转化成枚举,只要字符用逗号分隔。使用了flags之后就必须显示指定每个枚举项的值。
ConditionalAttribute
他的行为类似#if/#endif等预处理器标识符。但是它不能从程序集中消除CIL代码。我们只是给它一个无操作的行为。
需要预定义一个值 #define CONDITION_A, 然后把该值传递给Conditional("CONDITION_A").这样,被Conditional附着的构造就运行了。
该特性只能用于Method和Class,并且该函数不能有返回值或者带out参数。
ObsoleteAttribute
向调用者表明某个特定的成员或者类型已过时,如果对它进行调用,那么编译器会有一个警告信息。你也可以强制报错。
SerializableAttribute
表明可以序列化。
为了实现自定义的序列化,需要该Attribute并且实现ISerializable接口。这个接口只要实现GetObjectData()方法。
如果同时要实现反序列化,那么同时需要提供一个构造器来或者SerializationInfo和Serialization.StreamContext类型的参数。其实SerializationInfo对象就是由“name/value”对构成的集合。在序列化时,GetObjectData()会调用AddValue(). 反转的时候要调用某个Get*()方法。
序列化的版本控制。我们可以使用OptionalFieldAttribute来保证版本的兼容性。在新添加的字段加该属性。可惜这个特性在.net 2.0之后才支持,如果要支持之前的版本,那么在反序列化的函数里面对SerializationInfo进行遍历。
NoSerializableAttribute
不可序列化的字段。