应优先使用可回收的 AssemblyLoadContext 加载 DLL:创建 isCollectible=true 的上下文,调用 LoadFromAssemblyPath 加载,反射调用后执行 Unload 卸载;.NET Framework 中 Assembly.LoadFrom 不可卸载且易冲突,跨框架需运行时检测并谨慎回退。

用 AssemblyLoadContext 加载 DLL(.NET Core / .NET 5+ 推荐)
在 .NET Core 及更高版本中,Assembly.LoadFrom 已不推荐用于动态加载,因为它会将程序集加载进默认上下文,无法卸载,容易引发内存泄漏或类型冲突。应优先使用 AssemblyLoadContext 的隔离实例。
- 创建派生自
AssemblyLoadContext的子类,并重写Load方法(如需自定义解析依赖) - 用
new AssemblyLoadContext(isCollectible: true)构造可回收上下文 - 调用
context.LoadFromAssemblyPath(path)加载 DLL - 通过反射获取类型、创建实例、调用方法后,可调用
context.Unload()(注意:需 .NET 5+,且所有对该程序集的引用必须已释放)
示例关键片段:
var context = new AssemblyLoadContext(isCollectible: true);
var asm = context.LoadFromAssemblyPath(@"C:\plugins\MyPlugin.dll");
var type = asm.GetType("MyPlugin.Processor");
var instance = Activator.CreateInstance(type);
type.GetMethod("Run").Invoke(instance, null);
context.Unload(); // 不会立即卸载,但标记为可回收
Assembly.LoadFrom 在 .NET Framework 中仍可用,但有陷阱
Assembly.LoadFrom 在 .NET Framework 下能快速加载,但它会自动解析并加载同目录下的依赖项,且一旦加载进默认上下文,就无法卸载——哪怕你不再持有任何引用。
- 若多次调用
Assembly.LoadFrom加载同一路径的 DLL,返回的是缓存中的同一个Assembly实例(不是新加载) - 若 DLL 依赖其他未 GAC 的程序集,而它们不在当前应用目录或 probing path 中,会抛出
FileNotFoundException(错误信息里是“未能加载文件或程序集”,不是具体缺失的依赖名) - 不能用于加载与当前程序集同名但不同版本的 DLL(会直接返回已加载的版本)
调试建议:启用 Fusion Log(fuslogvw.exe)查看实际绑定过程,定位缺失依赖。
跨框架兼容写法:用 AssemblyLoadContext.Default.LoadFromAssemblyPath(.NET 5+)或回退到 Assembly.LoadFile(慎用)
如果项目需同时支持 .NET Framework 和 .NET 5+,不能无脑用 LoadFromAssemblyPath(.NET Framework 没这个 API)。但要注意:Assembly.LoadFile 是危险选项——它绕过上下文和探测逻辑,加载的程序集无法解析其依赖(除非手动处理 AppDomain.CurrentDomain.AssemblyResolve),极易失败。
- 优先检测运行时:
RuntimeInformation.FrameworkDescription或typeof(AssemblyLoadContext).IsAvailable - .NET 5+:走
AssemblyLoadContext.Default.LoadFromAssemblyPath(path)(不可卸载,但兼容简单场景) - .NET Framework:仅当确认无依赖或依赖已全局加载时,才用
Assembly.LoadFrom(path);否则必须自己实现AssemblyResolve事件来补全依赖查找逻辑
特别注意:LoadFile 加载的类型与主程序集中同名类型不兼容(即使代码完全一样),做 is 判断或强制转型会失败。
执行前必须验证程序集签名、目标框架和入口点
用户提供的 DLL 往往未经信任,直接 Load 可能引发安全或兼容性问题。不要跳过校验步骤。
- 用
AssemblyName.GetAssemblyName(path)提前读取元数据,检查GetReferencedAssemblies()是否包含危险项(如System.Drawing在 Linux 上不可用) - 检查
AssemblyName.Version和AssemblyName.ProcessorArchitecture是否匹配宿主环境 - 确认导出类型有无无参公共构造函数,方法是否为
public且非static(若用Activator.CreateInstance) - 若执行的是
Main方法,注意其签名必须是void Main(string[] args)或int Main(string[] args),且类型需标记为[STAThread]等属性(如需 UI)
最易忽略的一点:DLL 若含 NativeAOT 或 Trimmed 元数据,可能在反射调用时因裁剪丢失成员而静默失败——务必在构建插件时禁用 trimming 或保留必要反射入口。








