CLR Via C# 3rd 阅读摘要 -- Chapter 23 – Assembly Loading and Reflection
Assembly Loading
- 在运行时,JIT编译器使用程序集的TypeDef和AssemblyDef元数据表来确定被引用的类型在哪个程序集中;
- CLR内部尝试使用System.Reflection.Assembly.Load静态方法来装载程序集;
- 使用强名称程序集装载时,Load方法会引起CLR应用版本绑定重定向策略(GAC,应用程序根目录,私有路径子目录,代码基位置)。如果是弱名称程序集,Load方法不会应用版本绑定重定向策略,CLR也不会在GAC中查找程序集;
- Load方法string参数的形式:"SomeAssembly, Version=2.0.0.0, Culture=neutral, PublicKeyToken=01234567890abcde, ProcessorArchitecutre=MSIL";
- CLR目前支持的ProcessorArchitecture:MSIL,x86,IA64,AMD64;
- System.AppDomain.Load(...) (.net 2.0没有的)不同于System.Reflection.Assembly.Load(...),AppDomain.Load是一个实例方法,用来装载一个特定的程序集到指定的AppDomain,该方法是设计用来给非托管代码调用的,它允许一个宿主注入一个程序集到特定的AppDomain中。托管应用中应该避免使用AppDomain.Load方法;
- 相关的工具:ILDasm.exe, PEVerify.exe, CorFlags.exe, GACUtil.exe, SGen.exe, SN.exe, XSD.exe;
- 如果要指定路径装载程序集,可以使用Assembly.LoadFrom(...)方法,LoadFrom会首先调用System.Assembly.AssemblyName.GetAssemblyName(...),然后调用Load方法;
- LoadFrom(...)可以传入URL作为参数,但是建议使用另一个方法UnsafeLoadFrom(...);
- Assembly.LoadFile(...)可以多次装载同一个程序集到单独的AppDomain中,但是CLR不会自动解决程序集依赖问题,必须注册AppDomain.AssemblyResolve事件然后在事件回调方法中显示的装载相关的程序集;
- 程序集不可以被单独卸载,AppDomain才可以;
- 将引用的程序集dll作为项目的"嵌入的资源"再编译,可以包装成单独的exe分发,在运行时CLR找不到依赖的dll,这可以通过注册AppDomain.ResolveAssembly事件来解决:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { String resourceName = "AssemblyLoadingAndReflection." + new AssemblyName(args.Name).Name + ".dll"; using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) { Byte[] assemblyData = new Byte[stream.Length]; stream.Read(assemblyData, 0, assemblyData.Length); return Assembly.Load(assemblyData); } };
Using Reflection to Build a Dynamically Extensible Application
- System.Reflection命名空间,可以用来构建动态扩展的应用程序;
- 反射还可以用来在运行时从一个特定的程序集装载特定的类型来完成相关的任务。概念上跟Win32的的LoadLibrary和GetProcAddress方法相似;
- 早期绑定(编译时)和后期绑定(运行时)。
Reflection Performance
- 反射的两个主要缺点:
- 反射没有提供运行时的类型安全;
- 反射的速度很慢。通过反射来调用成员会损坏性能。
- 一般情况下应该避免使用反射,如果应用程序需要动态的发现和构造类型实例,可以选择下面两种捷径之一:
- 使得类型继承自编译时确定的基类型;
- (推荐)使得类型实现在编译时确定的接口。
- 使用上面两种方法时,应该把基类型或接口定义在独立的程序集中,这可以减轻版本问题;
- 发现定义在程序集中的类型:Assembly.GetExportedType(...);
- FLC中,Sytem.ReflectionOnlyType 继承自 Sytem.RuntimeType 继承自 Sytem.Type 继承自 System.Reflection.MemberInfo;
- TypeDelegator用来通过动态的封装Type来子类化一个Type;
- System.RuntimeType是FLC内部使用的,一个类型第一次在AppDomain中访问时,CLR构造一个RuntimeType的实例并初始化对象的字段到反射类型的信息;
- System.Object.GetType()实际上会返回一个RuntimeType;
- typeof操作符,通常用来比较早期绑定和晚期绑定的类型信息;
- 如何构造一个类型的实例?
- System.Activator.CreateInstance(), CreateInstanceFrom();
- System.AppDomain.CreateInstance(), CreateInstanceAndUpwarp(), CreateInstanceFrom(), CreateInstanceFromAndUnwarp();
- System.Type.InvokeMember();
- System.Reflection.ConstructorInfo.Invoke();
- System.Activator.CreateInstance()允许构造一个值类型而不必调用构造方法;
- 数组的动态构造:调用Array.CreateInstance(...)静态方法;
- 委托的动态构造:调用Delegate.CreateInstance(...)静态方法;
- 泛型类型的动态构造:调用Type.MakeGenericType(...)实例方法。
Designing an Application That Supports Add-Ins
- 当构建一个可扩展的应用程序时,应该以接口为中心点。当然也可以用基类代替接口,但通常接口优先,让插件开发者选择自己选择基类;
- 如何设计一个可扩展的应用程序?步骤:
- 创建一个宿主SDK程序集,定义方法接口,满足宿主应用和插件之间的通信机制。尽量使用MSCorLib.dll中定义的类型和接口,一旦确定别随意更改;
- 插件开发者,定义自己的类型在插件程序集中;
- 创建一个分离的宿主应用程序集包含你应用程序的类型,想干什么就干什么,但是别涉及插件宿主SDK。
Using Reflection to Discover a Type's Member
- 发现类型的成员,关于类型成员封装信息的反射类型的层次结构:
- System.Type继承自System.Reflection.MemberInfo,GetMembers()方法返回继承自MemberInfo(EventInfo, FieldInfo, MethodBase(ConstrcutorInfo, MethodInfo), PropertyInfo, Type)对象的数组;
- DeclaringType与ReflectedType属性的区别(比如,public class MyType: Object {public string ToString(){return null;}},typeof(MyType).GetMembers()):
- DeclaringType:获得定义该成员的类(ToString,返回MyType,Equals,返回Object);
- ReflectedType:获得用来获得该成员的类对象(ToString, Equals,都返回MyType)。
- 反射的对象模型:
- 如何筛选成员类型:System.Reflection.BindingFlags;Type.GetMembers(), GetNestedTypes(), GetFields(), GetConstructors(), GetMethods(), GetProperties(), GetEvents();
- 发现类型的接口:Type.FindInterfaces(), GetInterface(), GetInterfaces();
- 调用类型的成员,Type.InvokeMember();
- 反射使用CAS(代码访问安全)来确定不被滥用;
- 一次绑定,多次调用,提高效率;
- 使用绑定句柄(RuntimeTypeHandle, RuntimeFieldHandle, RuntimeMethodHandle)来减少进程的内存开销。
本章小结
本章讨论了如何构建可动态扩展的应用程序,以及如何通过反射来获得类型信息,创建类型的实例,动态的访问它们的成员。首先介绍了如何加载程序集以及如何通过反射来扩展应用,然后分析了反射会引起的性能问题以及如何减轻影响,然后介绍了构建插件应用程序的途径,最后讲了如何通过反射来调用类型的成员。