咱们在使用C# 语言的Assembly.Load 来加载托管程序集并使用反射功能时,通常须要先经过Assembly.Load(), Assembly.LoadFrom() 等方法将目标托管程序集加载到当前应用程序域中,而后生成对应实例,最后再进行调用实例的属性或者方法。html
通常状况下,咱们调用Assembly.Load 一类方法是不会出问题的,可是对于如下几种状况Assembly.Load 方法没法处理:缓存
咱们如今关注第四种状况,由于这种状况是最多见的。咱们思考如下几个问题:架构
1. 为何目标程序集的方法在运行时不容许再加载一次?app
准确地说是为何在一个应用程序域(AppDomain)中加载后的程序集默认不容许再另一个应用程序域中加载?这是由于在第一次加载应用程序集时,Assemlby.Load 方法会将此程序集锁住,以防止在本身使用过程当中应用程序集被其余应用程序修改(通常指删除)。这其实与Win32 API 中的CreateFile 函数行为相似,咱们都知道,在 Windows 中去占用一个文件最直接、最简单的方式就是调用 CreateFile API 函数来打开文件。具体请参照:框架
http://blog.csdn.net/xt_xiaotian/article/details/6362450 dom
2. Assembly.Load 方法可否实如今加载目标程序集时不锁定它?函数
咱们可使用以下代码加载咱们的程序集:优化
byte[] buffer = System.IO.File.ReadAllBytes(yourFullfileNamePath); //Load assembly using byte array Assembly assembly = Assembly.Load(buffer);
后台的实现是暂时先将目标程序集锁定,而后把程序集内容复制到内存中,读取后将程序集解锁并从内存中加载目标程序集的拷贝。spa
若是你须要经过上面的方法加载GAC 中的程序集,那么能够经过以下代码实现:.net
string assemblyName = "AssemblyTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=fffb45e56dd478e3"; Assembly ass = Assembly.ReflectionOnlyLoad(assemblyName); byte[] buffer = System.IO.File.ReadAllBytes(ass.Location); Assembly assembly = Assembly.Load(buffer
3. Assembly.Load 方法的缓存
在咱们使用Assembly.Load 系列方法加载目标程序集时,可能有各类状况致使加载失败,最多见的是目标程序集不存在而致使加载失败问题。失败后咱们可能想要再加载一次或者加载屡次直到成功为止,可是在.NET Framework 2.0 之后默认是没法实现的,缘由在于.NET Framework 2.0 之后 Assembly.Load 方法有缓存,第一次加载目标程序集的失败或者成功的状态都会被缓存,这样在你下一次加载目标程序集时不会真的加载,会直接从缓存里取目标程序集的内容和状态。
对这种状况咱们能够在调用Assembly.Load 方法的宿主程序的app.config 中加入以下配置信息来禁用缓存:
<?xml version="1.0"?> <configuration> <runtime> <disableCachingBindingFailures enabled="1" /> </runtime> <startup> <supportedRuntime version="v2.0.50727"/> </startup> </configuration>
4. 还有其余方案吗?
CLR 框架略图:
由于目前Assembly 的加载与卸载是彻底由应用程序域控制的,一个程序集只能够经过应用程序域加载,只能经过应用程序域的卸载而卸载,其余任何方式都不能够!!!
那么咱们能够在须要时将目标程序集加载进应用程序域,不须要时将应用程序域卸载,可是当前应用程序域的卸载只能经过关闭程序来实现。
到目前为止,看似无解了,可是咱们忽略了一个事实,那就是咱们能够在当前应用程序域中建立子应用程序域。能够经过在子应用程序域中进行程序集的加载,或者说只要涉及程序集加载的所有放在子应用程序域中,主应用程序域中不作任何与程序集加载有关的事情。
这部份内容我就不详细介绍了,你们能够参考:
http://www.codeproject.com/Articles/42312/Loading-Assemblies-in-Separate-Directories-Into-a
5. 咱们确实须要使用Assembly.Load吗?
由于Assembly.Load 是将整个程序集以及其相关的依赖程序集所有加载进来,只要有一个出错就会致使加载失败。若是咱们只是为了使用当前程序集的类型,而不是使用其方法或者属性的话就彻底能够抛弃Assembly.Load 方法。
微软在.Net Framework 2.0 时介绍了几个新的程序集加载APIs:
Assembly.ReflectionOnlyLoadFrom(String assemblyFile)
Assembly.ReflectionOnlyLoad(byte[] rawAssembly)
Assembly.ReflectionOnlyLoad(String assemblyName)
基于开篇提到的Assembly.Load 方法的5种问题, Reflection Only程序集加载APIs 能够:
使用时有以下几个注意事项:
1) CLR 不会搜索目标程序集所依赖的程序集,咱们必须经过ReflectionOnlyAssemblyResolve(Assembly.Load 中的对应事件是AssemblyResolve)事件手动处理。
2) 不能够在经过ReflectionOnly 方法加载进来的程序集执行任何方法,包括构造函数,只能够获取程序集的信息和类型。
3) 建议使用Assembly.ReflectionOnlyLoadFrom 方法,可是若是目标程序集在GAC中那么可使用Assembly.ReflectionOnlyLoad方法。
具体示例代码以下:
using System; using System.IO; using System.Reflection; public class ReflectionOnlyLoadTest { private String m_rootAssembly; public ReflectionOnlyLoadTest(String rootAssembly) { m_rootAssembly = rootAssembly; } public static void Main(String[] args) { if (args.Length != 1) { Console.WriteLine("Usage: Test assemblyPath"); return; } try { ReflectionOnlyLoadTest rolt = new ReflectionOnlyLoadTest(args[0]); rolt.Run(); } catch (Exception e) { Console.WriteLine("Exception: {0}!!!", e.Message); } } internal void Run() { AppDomain curDomain = AppDomain.CurrentDomain; curDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(MyReflectionOnlyResolveEventHandler); Assembly asm = Assembly.ReflectionOnlyLoadFrom(m_rootAssembly); // force loading all the dependencies Type[] types = asm.GetTypes(); // show reflection only assemblies in current appdomain Console.WriteLine("------------- Inspection Context --------------"); foreach (Assembly a in curDomain.ReflectionOnlyGetAssemblies()) { Console.WriteLine("Assembly Location: {0}", a.Location); Console.WriteLine("Assembly Name: {0}", a.FullName); Console.WriteLine(); } } private Assembly MyReflectionOnlyResolveEventHandler(object sender, ResolveEventArgs args) { AssemblyName name = new AssemblyName(args.Name); String asmToCheck = Path.GetDirectoryName(m_rootAssembly) + "\\" + name.Name + ".dll"; if (File.Exists(asmToCheck)) { return Assembly.ReflectionOnlyLoadFrom(asmToCheck); } return Assembly.ReflectionOnlyLoad(args.Name); } }
6. 为何没有Assembly.UnLoad 方法?
如下是CLR 产品单元经理(Unit Manager) Jason Zander 文章中的内容的整理:
1) 为了保证 CLR 中代码所引用的代码地址都是有效的,必须跟踪诸如 GC 对象和 COM CCW 之类的特殊应用。不然会出现 Unload 一个 Assembly 后,还有 CLR 对象或 COM 组件使用到这个 Assembly 的代码或数据地址,进而致使访问异常。为了不这种错误进行的跟踪,目前是在 AppDomain 一级进行的,若是要加入 Assembly.Unload 支持,则跟踪的粒度必须降到 Assembly 一级,这虽然在技术上不是不能实现,但代价太大了。 2) 若是支持 Assembly.Unload 则必须跟踪每一个 Assembly 的代码使用到的句柄和对现有托管代码的引用。例如如今 JITer 在编译方法时,生成代码都在一个统一的区域,若是要支持卸载 Assembly 则必须对每一个 Assembly 都进行独立编译。此外还有一些相似的资源使用问题,若是要分离跟踪技术上虽然可行,但代价较大,特别是在诸如 WinCE 这类资源有限的系统上问题比较明显。 3) CLR 中支持跨 AppDomain 的 Assembly 载入优化,也就是 domain neutral 的优化,使得多个 AppDomain 能够共享一份代码,加快载入速度。而目前 v1.0 和 v1.1 没法处理卸载 domain neutral 类型代码。这也致使实现 Assembly.Unload 完整语义的困难性。
详细请参考: http://www.cnblogs.com/ccBoy/archive/2004/07/13/23636.html
http://blogs.msdn.com/b/jasonz/archive/2004/05/31/145105.aspx
7. 须要牢记的经验
1) 只加载本身须要直接调用的程序集,不加载目标程序集内部引用的程序集和其余无关程序集。
2) 能使用RefelectionLoad 方法加载的程序集毫不要使用Assembly.Load 方法加载。
3) 一旦出现加载错误,不要显而易见认为是程序集不存在!要检查程序集加载缓存、是否出现同一程序集被不一样应用程序域加载状况等。
至此,咱们已经阐述了Assembly.Load 方法的一些特性,你已经了解它了吗?
参考连接:
http://msdn.microsoft.com/en-us/library/t07a3dye(v=vs.71).aspx
http://blogs.msdn.com/b/junfeng/archive/2004/11/03/252033.aspx
http://blogs.msdn.com/b/junfeng/archive/2004/08/24/219691.aspx
http://www.sosuo8.com/article/show.asp?id=2979
http://msdn.microsoft.com/en-us/library/ms404279.aspx
http://blog.csdn.net/xt_xiaotian/article/details/6362450
http://blogs.msdn.com/b/jasonz/archive/2004/05/31/145105.aspx
http://www.cnblogs.com/ccBoy/archive/2004/07/13/23636.html
http://www.cnblogs.com/wayfarer/archive/2004/09/29/47896.html
http://www.codeproject.com/Articles/42312/Loading-Assemblies-in-Separate-Directories-Into-a