assemblyloadcontext(alc)是.net core/.net 5+中唯一可卸载程序集的机制,需创建iscollectible=true的非默认alc,通过其loadfromassemblypath加载,并用委托或executeinloadcontext跨上下文调用,卸载前须清除所有引用并确保无后台线程或事件残留。

用 AssemblyLoadContext 加载 DLL,不是 Assembly.LoadFrom
直接调用 Assembly.LoadFrom 或 Assembly.LoadFile 会导致程序集永久驻留——哪怕你把它设为 null、清空引用、触发 GC,它也卸载不掉。这是 .NET Core / .NET 5+ 最常踩的坑:你以为卸载了,其实没卸。
真正可卸载的路径只有一条:AssemblyLoadContext(ALC)。它把程序集隔离在独立上下文中,支持显式卸载(Unload()),前提是这个 ALC 是“非默认”的(即构造时传 isCollectible = true)。
-
AssemblyLoadContext.Default永远不可卸载,所有传统加载方式默认都走它 - 必须新建一个 collectible ALC:
var alc = new AssemblyLoadContext(isCollectible: true) - 加载必须通过该 ALC 的
LoadFromAssemblyPath方法,不能用静态Assembly.Load*系列 - 加载后,类型反射、创建实例、调用方法,全部要在该 ALC 内完成,跨 ALC 直接传类型会报
System.Runtime.Serialization.SerializationException或类型不匹配
调用 DLL 中的方法前,得先解决“跨 ALC 类型访问”问题
你不能直接写 var obj = Activator.CreateInstance(type) 然后调用其方法——如果 type 来自 collectible ALC,而当前代码在默认 ALC,.NET 会拒绝操作,报错类似:Cannot load type 'XXX' from assembly 'YYY' because it is not in the same context。
可行方案只有两个,且都绕不开“边界代理”:
- 用
MarshalByRefObject+ 远程激活(仅限 .NET Framework,.NET Core+ 已移除 Remoting 支持) - 在目标 ALC 内部定义一个“桥接器”类(继承
MarshalByRefObject不再有效),然后通过接口抽象 +AssemblyLoadContext.GetLoadContext(assembly).ExecuteInLoadContext执行闭包逻辑 - 更常用的是:把要调用的逻辑封装成
Func<object></object>或Action委托,在 ALC 内部构造并返回,再在外部调用——委托本身是跨 ALC 安全的
示例关键片段:
var alc = new AssemblyLoadContext(isCollectible: true);
var asm = alc.LoadFromAssemblyPath(@"plugin.dll");
var entryType = asm.GetType("Plugin.Entry");
var entry = alc.LoadFromAssemblyPath(@"plugin.dll").CreateInstance("Plugin.Entry");
// ❌ 错误:entry 是 collectible ALC 中的对象,直接调用方法会失败
// ✅ 正确:用反射 + Invoke,或提前在 ALC 内准备好可序列化/委托化的入口
var method = entryType.GetMethod("DoWork");
method.Invoke(entry, null); // ⚠️ 仍可能失败,取决于方法内部是否引用其他 collectible 类型
卸载失败?检查是否有任何托管对象还在引用该 ALC
alc.Unload() 不是立即生效的异步操作,它会发起卸载请求,但只有当该 ALC 中所有对象都被 GC 回收、且无任何强引用残留时,才会真正完成。常见卡住原因:
- 你在默认 ALC 中保存了来自该 ALC 的
Type、MethodInfo、Delegate实例(它们隐式持有 ALC 引用) - 插件 DLL 启动了后台线程、注册了事件(尤其是静态事件)、开启了
Timer,且未在卸载前清理 - 使用了
AssemblyLoadContext.Default.Resolving事件,并在其中返回了该 collectible ALC 中的程序集(形成循环引用) - 日志框架(如 Serilog、NLog)缓存了插件中的类型名或属性,导致引用泄漏
验证是否卸载成功:调用 alc.Unload() 后,手动触发 GC.Collect(); GC.WaitForPendingFinalizers();,再检查 alc.Assemblies 是否为空、alc.IsUnloaded 是否为 true。
.NET Framework 下没有 AssemblyLoadContext,只能靠 AppDomain(已废弃)
.NET Framework 4.x 虽然支持 AppDomain 隔离和卸载,但它在 .NET Core / .NET 5+ 中被彻底移除,且即便在旧框架下也极不稳定:AppDomain 卸载后无法完全回收内存,容易引发 Cannot unload an AppDomain 或句柄泄漏。微软官方已标记为“不推荐用于新开发”。
如果你必须兼容老框架,唯一较稳妥的做法是:进程外加载(启动子进程运行插件,通过 IPC 通信),而不是进程内动态加载。否则,所谓“卸载”,基本等于“等进程退出”。
现代项目请坚定使用 AssemblyLoadContext,并接受它的约束:一切跨 ALC 操作必须显式桥接、所有资源必须主动释放、卸载不是命令而是协作结果。










