求知若饥,虚心若愚
反射
可以用反射做下面这些事情
- 访问程序集中类型的元数据。包括像完整类型名和成员名这样的构造,以及对一个构造进行修饰的任何特性。
- 使用元数据在运行时动态调用类型的成员,而不是使用编译时绑定。
使用System.Type访问元数据
GetMethods并不能返回拓展方法,拓展方法只能作为实现类型的静态成员使用。
- 类型的名称(Type.Name)
- 类型是public吗(Type.IsPublic)
- 类型的基类型是什么(Type.BaseType)
- 类型支持任何接口吗(Type.GetInterfaces())
- 类型在哪个程序集中定义(Type.Assembly)
- 类型的属性、方法、字段是什么(Type.GetProperties()、Type.GetMethods()、Type.GetFields())
- 都有什么特性在修饰一个类型(Type.GetCustomAttributes())
获取Type
- GetType(),要求对实例对象使用
- typeof(),使用类类型即可
泛型类型上的反射
- Type.IsGenericType来指示类型是否泛型
- Type.ContainsGenericParameters判断类或者方法是否包含尚未设置的泛型参数
- Type.GetGenericArguments()方法从泛型类获取泛型实参(或类型参数)的列表,System.Type实例构成的数组
nameof操作符
获取被指定为实参的任何程序元素的非限定名称,这里发生在编译器,所以其实与反射无关。但他确实接收有关程序集及其结构的数据。
特性
特性的作用是在程序集中插入额外元数据,将元数据同编程构造(比如类、方法或者属性)关联。
- 同一构造可多个特性,可以多个方括号或者一个方括号里面用逗号隔开。
- 应用特性时通常都不添加该后缀(Attribute),只有在自定义特性或者以内联方式使用特性时才需要添加后缀。
- 特性是对象,从System.Attribute派生
- 可通过GetCustomAttributes来获取特性,可传入指定特性查找,不传为查找所有特性。查找特性的代码可作为静态函数放在特性类的实现中。
- 特性还可封装数据,但只有常量值和typeof表达式才允许作为实参构造
- 预定义特性包含了只有CIL提供者或者编译器才能提供的特定行为
预定义特性 作用 AttributeUsageAttribute 修饰特性,限制特性允许输入的实参或者特性修饰位置 FlagsAttribute 修饰枚举,可以重写枚举值的ToString和Parse方法,使得字符串和枚举值可以互转,还支持多个枚举值合并写法,生成的字符串会用逗号隔开。 ObsoleteAttribute 用于编译代码版本,向调用者指出一个特定的成员或类型已过期 ConditionalAttribute 提供类似#if/#endif的条件编译功能,在编译时会将不符合条件的移除编译 SerializableAttribute 有这个特性,formatter(格式化程序)类就可对序列化对象执行反射,并把它拷贝到一个流中。可配合NonSerializable对类中不可(不允许)序列化的字段进行标记。
使用动态特性进行编程
使用动态对象(c#4.0引入)进行编程,开发人员可通过动态调度机制对设想的操作进行编码。“运行时”会在程序执行时对这个机制进行解析,而不是由编译器在编译时验证和绑定。
使用dynamic调用反射
1 | var type = typeof(T); |
- dynamic是通知编译器生成代码的指令,当“运行时”遇到一个dynamic调用时,它可以将请求编译成CIL,在调用新编译的调用
- dynamic修饰的类型,相当于进行了一次包装(wrap),编译时不会验证,只有解释调用时才会执行。类似执行时才推导类型,所以对它使用gettype获得的是基础类型而不是dynamic。
- 任何能转换为object的类型都能转换成dynamic
- 值类型转换dynamic要装箱
- dynamic的基础类型在每次赋值时都可能改变
- 任何dynamic成员调用都返回dynamic对象
- 用dynamic实现的反射不支持扩展方法
- dynamic其实类似于Object,但它的动态行为只会在编译时出现
- 如果编译时不能验证,使用动态类型会使代码显得更易读、简介,当然能验证的话静态编译是首选。
- 自定义dynamic对象需要实现System.Dynamic.IDynamicMetaObjectProvider接口,但更推荐直接基础System.Dynamic.DynamicObject,避免实现一些默认成员