我前天写的一篇文章《c#获取程序集信息》中,描述了如何实用System.Reflection下Assembly类对程序集进行动态的加载、获取信息并调用其中的类和方法,但Assembly只提供了加载,并没提供卸载方法。
这时由于.net的应用程序域的底层实现机制所致,更细节的技术特性这里就不叙述了,本文就先说一下简单的原因,并给出一个解决的方法。
Assembly只能动态加载,不提供卸载方法,如果想卸载一个Assembly实例,只能是通过卸载这个Assembly所在的容器,就是他所在的应用域AppDomain。每个运行中的程序集实例都有一个AppDomain,可以看作将相关的程序集都放到这个域的容器里运行一样。那么,动态加载的Assembly程序集实例,不能直接卸载,要卸载他的应用域,但默认是加载到主程序集本身的应用域appdomain中了,只能通过关闭程序来卸载,哈哈,这当然不是我们想要的。
好在.net允许动态的创建另外一个容器Appdomain,这样就可以通过新建一个appdomain,在这个新建的域里加载要动态加在的程序集,然后调用其中的程序集,不需要的时候、需要卸载的时候,就可以将这个临时创建的appdomain卸载,从而达到动态加载和卸载assembly的效果。
实现代码如下:
- //测试的类,注意这里的MarshalByRefObject,有了这个,这个类才能在远程跨域调用中序列化传送,从而不发生访问异常。
- public class ModuleManage : MarshalByRefObject
- {
- public void GetDesc(string filename,ref string desc)
- {
- //首先为创建appdomain做准备,创建一个appdomainsetup,此实例包含了后面创建appdomain所需的一些参数
- AppDomainSetup setup = new AppDomainSetup();
- setup.ApplicationName = "Test";
- setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory ;
- setup.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "private");
- setup.CachePath = setup.ApplicationBase;
- setup.ShadowCopyFiles = "true";
- setup.ShadowCopyDirectories = setup.ApplicationBase;
- //创建应用域appdomian
- AppDomain appDomain = AppDomain.CreateDomain("TestDomain", null, setup);
- //在新建的应用域中创建本程序集本身的实例,呵呵,就是本程序集在此加载一份
- string aname = Assembly.GetExecutingAssembly().GetName().FullName;
- string tname = this.GetType().FullName;
- object m = appDomain.CreateInstanceAndUnwrap(
- aname, tname);
- //调用在临时域中运行的本类的方法_GetDesc,该方法实现了加载和获取一个程序集的描述信息
- desc = ((ModuleManage)m)._GetDesc(filename);
- //最后使用完毕了,就卸载临时域,这样动态动态创建的域、另一个本类的实例、动态加载的那个assembly,都被卸载
- AppDomain.Unload(appDomain);
- }
- //此方法在临时创佳的域中创建本类实例并进行调用,最终将随着临时域卸载而销毁。
- public string _GetDesc(string filename)
- {
- Assembly a = Assembly.LoadFile(filename);
- return ((AssemblyDescriptionAttribute)Attribute.GetCustomAttribute(a, typeof(AssemblyDescriptionAttribute))).Description;
- }
- }
上面的代码描述的类,仅仅是我测试所写,实现了动态加载一个程序集并获取该程序集的描述信息,并且动态的卸载,从而不会造成该程序集的dll文件被锁定。代码中的注释已经描述的足够详细,有兴趣的可以认真看下注释。