结论:.NET 5+ 新项目必须用可卸载的 AssemblyLoadContext,.NET Framework 或仅反射查看可用 Assembly.LoadFrom();后者无法卸载且易引发版本冲突,依赖需手动处理或复制到主程序目录。

直接说结论:C# 加载 DLL 程序集,优先用 AssemblyLoadContext.LoadFromAssemblyPath()(.NET Core 3.0+)或 Assembly.LoadFrom()(传统 .NET Framework),但必须区分“仅反射查看”和“实际执行代码”两种需求——后者对依赖解析、版本冲突、加载上下文隔离更敏感,一步错就抛 FileNotFoundException 或 FileLoadException。
什么时候该用 Assembly.LoadFrom(),什么时候必须换 AssemblyLoadContext?
简单判断:如果你的程序是 .NET Framework 4.x 项目,或者只是临时读取 DLL 的类型信息(比如做插件扫描),Assembly.LoadFrom("plugin.dll") 足够快、够直白;但只要涉及多版本插件共存、卸载重载、或 .NET 5/6/7+ 项目,就必须用 AssemblyLoadContext ——因为 Assembly.LoadFrom() 在新运行时里会偷偷把 DLL 加进默认上下文,无法卸载,内存泄漏风险高。
常见错误现象:Assembly.LoadFrom() 成功后,第二次加载同名不同版本 DLL 报 FileLoadException: 无法在同一上下文中加载两个不同版本的程序集。
实操建议:
- 新项目(.NET 5+)一律用
AssemblyLoadContext,哪怕只加载一次 - 旧项目迁移时,先确认是否真需要卸载——如果只是启动时加载插件且不重启进程,
LoadFrom仍可接受 -
Assembly.LoadFile()已被标记为“不推荐”,它绕过 GAC 和依赖解析,极易导致MissingMethodException,别碰
AssemblyLoadContext 自定义上下文怎么写才不踩坑?
核心不是“怎么写”,而是“要不要重写 Load 方法”。多数人以为必须 override Load,其实 90% 场景只需构造一个 AssemblyLoadContext 实例并传 isCollectible = true,然后用它的 LoadFromAssemblyPath()。
示例(安全加载并预留卸载能力):
var context = new AssemblyLoadContext(isCollectible: true); var asm = context.LoadFromAssemblyPath(@"C:\plugins\MyPlugin.dll"); // ... 使用 asm.GetType()、Activator.CreateInstance() 等 context.Unload(); // .NET 5+ 支持,触发 GC 回收
容易忽略的点:
- 必须显式调用
context.Unload(),否则即使isCollectible = true也无效 - 卸载前,确保没有线程正在执行该上下文中的代码,否则抛
InvalidOperationException - 依赖项(如 MyPlugin.dll 引用 Newtonsoft.Json.dll)不会自动加载——要么放同一目录让上下文自动探测,要么在
Load方法里手动处理assemblyName
为什么 Assembly.LoadFrom() 找不到依赖 DLL?
因为 LoadFrom 只负责加载指定路径的主 DLL,其余依赖按标准绑定规则查找:先查 GAC,再查调用方程序集所在目录,最后查 AppDomain.CurrentDomain.AssemblyResolve 事件注册的处理器——它**不查主 DLL 所在目录**(除非你手动设置 AppDomain.CurrentDomain.BaseDirectory)。
典型表现:主 DLL 加载成功,但一调用其方法就崩在 System.IO.FileNotFoundException,提示找不到某个依赖项(比如 log4net.dll)。
解决路径:
- 把所有依赖 DLL 复制到主程序的输出目录(bin/Debug),最省事
- 订阅
AppDomain.CurrentDomain.AssemblyResolve,在事件里检查args.Name是否匹配已知插件依赖,并从插件目录返回Assembly.LoadFrom() - .NET Core+ 推荐改用
AssemblyDependencyResolver解析依赖路径:var resolver = new AssemblyDependencyResolver(assemblyPath); var depPath = resolver.ResolveAssemblyToPath(assemblyName);
真正麻烦的从来不是“怎么加载”,而是“依赖在哪”“谁在用它”“能不能干净卸掉”。尤其当多个插件引用不同版本的同一个库时,AssemblyLoadContext 的隔离性就不是可选项,是必选项。别等上线后查内存泄漏才想起看上下文是否 collectible。


















