使用Assembly.LoadFrom()加载的程序集如何卸载?

61

我需要在加载dll后检查运行GetTypes()所需的时间。 代码如下。

Assembly assem = Assembly.LoadFrom(file);
sw = Stopwatch.StartNew();
var types1 = assem.GetTypes();
sw.Stop();
double time1 = sw.Elapsed.TotalMilliseconds;

我想卸载和重新加载dll,以检查运行GetTypes()所需的时间。

  • 如何卸载它? assem = null足够吗?
  • 有没有显式的方法调用垃圾收集器来回收分配给assem的资源?

5
简短回答是,您无法卸载程序集;只能卸载应用程序域。当您将程序集加载到新的应用程序域中时,会有一些限制,建议您对此进行一些研究,以了解它们的工作方式。尽管如此,即使之前的程序集未被卸载,您仍可以再次加载相同的程序集。 - Dan Bryant
有人试过如果将程序集设置为 null 会发生什么吗?它们是否会被 垃圾回收 或以任何方式释放?我担心内存问题。 - Bitterblue
这里有一个非常相关的帖子链接 - RBT
6个回答

72

你可以使用另一个AppDomain吗?

AppDomain dom = AppDomain.CreateDomain("some");     
AssemblyName assemblyName = new AssemblyName();
assemblyName.CodeBase = pathToAssembly;
Assembly assembly = dom.Load(assemblyName);
Type [] types = assembly.GetTypes();
AppDomain.Unload(dom);

11
对我来说失败了:在dom.Load处抛出了“FileNotFoundException”。有趣的是,文件确实存在,并且异常消息包含了文件的确切版本(不是1.0.0.0,所以我确定它最终找到了我的文件...)。 - AFract
2
在我的情况下,“FileNotFoundException”是由其中一个引用程序集引起的。最终我使用了Assembly.ReflectionOnlyLoadFrom和一个将所有引用程序集加载到反射上下文中的方法。 - Xtr
由于某些原因,这会加载位于应用程序目录中的与名称相同(如所述)的程序集,而不是在所提到的目录中的程序集...有任何想法为什么会发生这种情况吗? - mrid
1
不幸的是,这对于.NET Core不起作用,因为AppDomains未实现。 - Roger Far
System.PlatformNotSupportedException: '此平台不支持辅助应用程序域。' - Stef Heyenrath

54

你可以使用File.ReadAllBytes()代替LoadFrom()LoadFile(),并与Load一起使用。这样它不会使用程序集文件,而是读取数据并使用。

你的代码将会像这样:

Assembly assem = Assembly.Load(File.ReadAllBytes(filePath));
sw = Stopwatch.StartNew();
var types1 = assem.GetTypes();
sw.Stop();
double time1 = sw.Elapsed.TotalMilliseconds;

这里得知,只有在卸载包含它的所有应用程序域后,我们才能卸载该文件。

希望这可以帮助:)


2
你不能将字节数组传递给 Assembly.LoadFrom()。我编辑了你的帖子,但有人改回来了。 - roli09
1
@roli09 实际上,你可以这样做,并且它非常有效。我动态地将dll作为插件加载,然后在卸载它们的时候应该擦除文件。Hardik方法完美地运行,没有AppDomain的麻烦或其他问题。 roli09 在对注释进行任何修改之前,您应该测试代码,这应该是正确的答案。 - tapatio
2
@tapatio 这个方法能够正常工作是因为Hardik现在使用的是Assembly.Load而不是Assembly.LoadFrom - roli09
2
@Hardik 是正确的:Assembly Load 方法会保留对已加载文件的引用,防止该文件被另一个进程覆盖或删除。因此,使用 File 对象的 ReadAllBytes 方法是一个巧妙的解决方法,它不会保留对文件的引用。一旦读取了这些字节并关闭了文件,结果就会传递给 Assembly Load 方法。非常聪明! - Jazimov
1
但是,当您的程序集具有依赖项时,这种方法不起作用。我们将会收到 LoaderExceptions 错误。那么在这种情况下该怎么办呢? - coding_cs
显示剩余3条评论

39

很遗憾,一旦程序集被加载,就无法卸载它。但是你可以卸载一个应用程序域(AppDomain)。你可以创建一个新的应用程序域(AppDomain.CreateDomain(...)),将程序集加载到该应用程序域中进行操作,然后在需要时卸载该应用程序域。当卸载应用程序域时,所有已加载的程序集都将被卸载。(参见文献

要调用垃圾回收器,可以使用

GC.Collect(); // collects all unused memory
GC.WaitForPendingFinalizers(); // wait until GC has finished its work
GC.Collect();

垃圾回收(GC)会在后台线程中调用终结器,这就是为什么您必须等待并再次调用 Collect() 以确保您删除了所有内容的原因。


2
我正在加载(并可能卸载)数十个dll。创建70个AppDomains的性能影响(以及其他相关影响)是什么?如果可能的话,我应该避免这样做吗? - pushkin
我认为如果仅限于这个操作的话那样做是可行的,但是在我实际想要我的应用程序从该程序集中实例化一些对象、使用它们、销毁它们并且卸载该程序集的情况下,将其加载到单独的AppDomain中,会导致一些访问性上的麻烦吗? - Matheus Rocha

4
您无法从当前AppDomain卸载程序集。但是您可以创建新的AppDomain,将程序集加载到其中,在新的AppDomain中执行一些代码,然后卸载它。请查看以下链接:MSDN

4

如果你只想加载初始程序集而不加载它的任何依赖程序集,你可以在一个AppDomain中使用Assembly.LoadFile方法,然后在完成后卸载该AppDomain。

创建一个加载器类来加载并处理程序集:

class Loader : MarshalByRefObject
{
    public void Load(string file)
    {
        var assembly = Assembly.LoadFile(file);
        // Do stuff with the assembly.
    }
}

可以像这样在一个单独的应用程序域中运行加载程序:

var domain = AppDomain.CreateDomain(nameof(Loader), AppDomain.CurrentDomain.Evidence, new AppDomainSetup { ApplicationBase = Path.GetDirectoryName(typeof(Loader).Assembly.Location) });
try {
    var loader = (Loader)domain.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName);
    loader.Load(myFile);
} finally {
    AppDomain.Unload(domain);
}

0

很遗憾,汇编无法卸载。而且,如果您使用应用程序域,则会阻止您与主应用程序的API /程序集进行通信。

有关问题的最佳描述可以在此处找到:

脚本托管指南 http://www.csscript.net/help/Script_hosting_guideline_.html

如果您想在不与主应用程序通信的情况下运行C#代码,则最好的方法是集成C#脚本API:

https://github.com/oleg-shilo/cs-script/tree/master/Source/deployment/samples/Hosting/Legacy%20Samples/CodeDOM/Modifying%20script%20without%20restart

集成所需的软件包如下:

C#脚本: http://www.csscript.net/CurrentRelease.html

Visual Studio扩展: https://marketplace.visualstudio.com/items?itemName=oleg-shilo.cs-script

如果您想从C#脚本与应用程序进行通信,则目前唯一的方法是使用相同的appDomain和不断更改的程序集名称,但这会消耗大量内存和磁盘空间。

如何执行代码示例可以在此处找到:

https://github.com/tapika/cppscriptcore CsScriptHotReload.sln

这里是演示视频:

https://drive.google.com/open?id=1jOECJj0_UPNdllwF4GWb5OMybWPc0PUV


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接