读书笔记—CLR via C#反射

时间:2021-06-10 00:36:09

前言

这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享

程序集加载

  • AppDomain.Load
    • 尽量避免使用此方法加载程序集,因为Assembly不是从System.MarshalByRefObject派生,所以程序集对象必须按值封送回发出调用的那个AppDomain,但是CLR会使用发出调用的那个AppDomain的设置来定位并加载程序集。如果搜索策略和位置没有找到程序集,会抛出FileNotFoundException异常
  • Assembly.Load  推荐的程序集加载方式
  • Assembly.LoadFrom 支持从URI加载程序集
  • Assembly.LoadFile 加载程序集时,CLR不会自动解析任何依赖性问题;代码必须向AppDomain的AssemblyResolve事件登记,并让事件回调方式显式地加载任何依赖的程序集

反射的缺陷

  1. 编译时无法保证类型安全性。严重依赖字符串,丧失编译时的类型安全性
  2. 反射速度慢。加载IO并扫描元数据表,字符串搜索,字符串搜索执行的时不区分大小写的比较,这会进一步影响速度

Type类型

  • System.Type类型是执行类型和对象操作的起点。是从MemberInfo派生的抽象基类,具体派生类包括RuntimeType,ReflectionOnlyType,TypeDelegator,以及System.Reflection.Emit命名空间中的一些类型(包括EnumBuilder,GenericTypeParameterBuilder和TypeBuilder)
  • TypeDelegator允许代码封装一个Type对象,从而动态生成Type的一个子类,这样一来,就可以让原始Type负责处理大部分工作,同时重写一些需要自定义的功能。这个强大的机制允许重写反射的工作方式
  • 一个类型在AppDomain中被首次访问时,CLR会构造RuntimeType的一个实例,并初始化这个RuntimeType对象的字段以及Reflect关于类型的信息
  • 实际上Object的GetType非虚实例方法,晚期推断,返回的时RuntimeType对象的一个引用。RuntimeType是FLC内部定义的
  • 在一个AppDomain中,每个类型只有一个RuntimeType对象,所以可以使用相等和不等操作符进行判断
  • Type.GetType和重载版本也是一样返回RuntimeType对象引用
  • Type.ReflectionOnlyGetType,类型加载到一个“仅反射”的上下文中,不能执行
  • 实例方法GetNestedType和GetNestedTypes返回嵌套的子类型
  • MakeArrayType(int n) 创建n维数组
  • MakeByRefType方法: public void Test(ref Dock d); 通过反射获取方法参数类型 ParameterType == typeof(Dock).MakeByRefType()
  • Type的实例属性包括IsPublic,IsSealed,IsAbstract,IsClass和IsValueType,还有BaseType获得基类型等等
  • 动态构造类型
    • System.Activator.CreateInstance方法,还可以创建派生自MarshalByRefObject的System.Runtime.Remoting.ObjectHandle类型的远程对象,远程对象支持跨域传递,具体化时会调用ObjectHandle的Unwrap方法,如果对象按引用封送,会创建代理类型的对象。如果对象按值封送,对象的副本会被序列化。此方法还允许在不调用构造器的情况下创建值类型的一个实例
    • Activator.CreateInstanceFrom通过字符串指定类型及其程序集,程序集使用Assembly.LoadFrom加载到AppDomain中,由于不接受Type参数,返回的都是ObjectHandle对象引用,必须调用ObjectHandle的Unwrap方法进行具体化
    • AppDomain的方法,CreateInstance,CreateInstanceAndUnwrap,CreateInstanceFrom,CreateInstanceFromAndwrap
    • System.Type的InvokeMember实例方法,可使用一个Type对象引用来调用InvokeMember方法,查找与传递的实参匹配的一个构造器,并构造类型
    • System.Reflection.ConstructorInfo的Invoke实例方法
    • 数组、泛型、委托需要特殊的方式进行动态创建
      • 创建数组除了MakeArrayType,还有Array的静态CreateInstance方法。第一个参数都是对数组元素的Type的一个引用
      • 创建委托需要调用Delegate的静态CreateDelegate方法。第一个参数都是对想要创建的委托实例的Type的一个引用
      • 泛型类型的实例,需要使用MakeGenericType实例方法,将开放类型变成实际封闭类型
  • 注意DeclaringType和ReflectedType属性,一个是声明该成员的Type(类声明就是类,基类声明就是基类),一个是用于获取该成员的Type(返回MemberInfo所属于的那个类型,就是执行反射的类型)

类型成员

读书笔记—CLR via C#反射

  • GetMembers获取所有成员
  • 返回特定成员就是GetNestedTypes, GetFields, GetConstructors, GetMethods, GetProperties, GetEvetns方法
  • 获取参数,调用GetParameters方法获取由ParametereInfo对象构成的一个数组
  • 查询只读属性ReturnParameter获得ParameterInfo对象获得成员返回值的详细信息
  • 泛型类型或方法,调用GetGenericArguments获得类型参数的集合
  • 对于任何一项都可以调用GetCustomAttributes方法获得应用于它们的自定义attribute的一个集合
  • BindingFlags, 筛选返回的成员种类
    • BindingFlags.FlattenHierarchy  返回基类型定义的静态成员
    • 如果指定Public或NonPublic,必须同时指定Instance|Static,否则将不返回成员
    • 调用指定成员的方法比如GetMethod或GetMethod,传递字符串,这是BindingFlags.IgnoreCase标志就有用了
  • 获取一个类型继承的接口集合
    • 调用Type类型的FindInterfaces(找到接口)
    • GetInterface或GetInterfaces方法(获取接口详细信息)
    • 返回代表接口的Type对象。注意,它扫描类型的继承层次结构,并返回在指定类型及其所有的基类型上定义的所有接口
    • 可能针对多个接口定义同一个方法,为了获得一个特定接口的MethodInfo对象,调用Type的GetInterfaceMap实例方法(传递接口类型作为实参)。返回System.Reflection.InterfaceMapping(一个值类型)的一个实例(包含了类型和方法的一些定义)
  • 对于字段或属性,可以获取值,对于构造函数或方法可以执行call获得返回值,PropertyInfo调用call属性的get和set访问其方法,EventInfo则可以通过Add或Remove方法添加或删除一个事件处理程序
  • Type类提供InvokeMember方法,可通过它调用一个成员
  • InvokeMember内部执行两个操作。首先,选择要调用的一个恰当的成员---绑定(bingding)其次,必须实际调用成员---调用(invoking)
  • Binder:从System.Reflection.Binder抽象类型派生的类型。提供BindToField, BindToMethod, ChangeType等方法,Microsoft定义了System.DefaultBinder内部使用
  • Type的所有的Get和Set系列的方法,在内部实际上都是调用InvokeMember,只是BindingFlags设置的区别
  • 性能的考量,一次绑定,多次调用。将MemberInfo保存起来
  • MemberInfo的实例方法都有一个重载版本要获取对Binder派生对象的引用以及一些BindingFlags,其实不会引起重绑定。调用这些方法时,要在Binder派生对象的帮助下,对提供的方法参数进行类型转换,BindingFlags唯一可以传递的标志时BindingFlags.SuppressChangeType。调用MemberInfo的Invokemethod来绑定并调用一个成员时,SuppressChangeType和ExactBinding可能都要指定,或者都不指定
  • Type和MemberInfo派生对象需要大量内存保存。对存储的性能也会有影响。替代方案就是使用FCL定义的句柄类型
  • 句柄类型:RuntimeTypeHandle, RuntimeFieldHandle, RuntimeMethodHandle。都是值类型,只包含一个IntPtr的字段,是一个句柄,引用了AppDomain的Loader堆中的一个类型、字段或方法。简单、高效的方式将重量级的Type或MemberInfo转换为轻量级的运行时句柄实例
  • 将Type转换为RuntimeTypeHandle,调用Type.GetTypeHandle,并传递那个Type对象引用,反过来转换,调用 Type.GetTypeFromHandle,并传递那个RuntimeTypeHandle. 其他的派生Memberinfo都有对于的方法转换为句柄

读书笔记—CLR via C#反射

Tips

  • 生成程序集或模块时,编译器创建类型定义表、字段定义表、方法定义表等其他表,System.Reflection命名空间的类型为程序集或模块中包含的元数据创建了一个对象模型
  • 应用程序显式加载程序集,构造类型实例,再调用类型中定义的方法。以这种方式绑定并调用方法通常称为晚期绑定
  • 发现程序集中的类型 Assembly.GetExportedTypes
  • 晚期绑定使用 typeof(NeedType).IsAssignableFrom(t) 判断t对象是否兼容(实现或派生于)NeedType
  • 建议:为了获得较好的性能和编译时的类型安全,尽量避免使用反射。在动态可扩展应用程序下,构造好一个对象只恨,宿主代码一般要将对象转型为编译时已知的一个接口或者基类。这样一来,访问对象的成员时就可以获得较高的性能,而且可以确保编译时的类型安全性

后记

不得不说,这本书信息量真的是很大,以至于做笔记做完了都觉得没有好好整理和归纳,此笔记仅限于自己记录和使用,如果少许地方存在纰漏或勘误的情况,本人概不负责,最终还是要参考书本来学习和理解