如何在更新依赖程序集时正确处理AppDomain.AssemblyResolve事件
AssemblyResolve事件触发逻辑与程序集重载解决方案
一、AssemblyResolve事件的触发条件
CLR只会在尝试加载程序集失败时触发这个事件:
- 比如程序集文件找不到、强名称签名不匹配、版本号不兼容等场景。
- 一旦CLR通过事件(或其他方式)成功解析了某个程序集的标识(名称、版本、公钥、文化),就会把这个解析结果缓存到当前应用程序域的程序集缓存里。后续再请求相同标识的程序集时,直接用缓存,不会再触发AssemblyResolve事件——这就是你更新后仍用旧程序集的核心原因。
二、解决更新重载问题的高效方案
1. 用应用程序域(AppDomain)隔离加载(.NET Framework)
每个脚本引擎实例对应一个独立的AppDomain:
- 加载程序集时,把程序集加载到专属的AppDomain里,而非默认域。
- 当需要更新程序集时,直接卸载旧的AppDomain(
AppDomain.Unload()),然后创建新的AppDomain加载最新的程序集文件。 - 这种方式完全隔离了新旧程序集的缓存,新域加载时会重新触发AssemblyResolve事件,不受旧缓存影响。
- 代码示例:
// 创建新应用域 var domainSetup = new AppDomainSetup { ApplicationBase = AppDomain.CurrentDomain.BaseDirectory }; var scriptDomain = AppDomain.CreateDomain("ScriptDomain_" + Guid.NewGuid(), null, domainSetup); // 在新域中加载程序集 var scriptEngine = (ScriptEngine)scriptDomain.CreateInstanceAndUnwrap( typeof(ScriptEngine).Assembly.FullName, typeof(ScriptEngine).FullName ); scriptEngine.LoadAssemblies(updatedAssemblyPaths); // 更新时卸载旧域 AppDomain.Unload(scriptDomain);
2. 用自定义AssemblyLoadContext隔离(.NET Core/.NET 5+)
.NET Core及以后推荐用AssemblyLoadContext替代AppDomain:
- 每个脚本引擎实例对应一个自定义的AssemblyLoadContext,重写
Load方法来处理依赖解析,替代AssemblyResolve事件。 - 更新时,直接丢弃旧的AssemblyLoadContext实例(确保无引用),创建新的上下文加载最新程序集。每个上下文的缓存独立,新上下文不会复用旧程序集的解析结果。
- 代码示例:
public class ScriptAssemblyLoadContext : AssemblyLoadContext { private readonly string[] _assemblyPaths; public ScriptAssemblyLoadContext(string[] assemblyPaths) : base(isCollectible: true) { _assemblyPaths = assemblyPaths; } protected override Assembly? Load(AssemblyName assemblyName) { // 检查是否为目标脚本程序集 var targetPath = _assemblyPaths.FirstOrDefault(p => Path.GetFileNameWithoutExtension(p).Equals(assemblyName.Name, StringComparison.OrdinalIgnoreCase) ); if (targetPath != null) { return LoadFromAssemblyPath(targetPath); } // 其他依赖交给默认加载逻辑 return null; } } // 使用方式 var loadContext = new ScriptAssemblyLoadContext(updatedAssemblyPaths); var scriptAssembly = loadContext.LoadFromAssemblyPath(scriptPath); // 更新时丢弃旧上下文,创建新实例 loadContext = null; // 等待GC回收旧上下文及程序集 var newLoadContext = new ScriptAssemblyLoadContext(newAssemblyPaths);
3. 应急缓存规避方案
如果暂时无法使用隔离方案,可以通过修改程序集版本号或添加自定义属性改变程序集标识,让CLR认为是新程序集,从而重新触发AssemblyResolve事件。但这只是临时 workaround,不如隔离方案彻底。
三、操控已加载程序集的注意事项
- .NET中无法直接卸载单个已加载的程序集,只能通过卸载整个AppDomain(.NET Framework)或回收AssemblyLoadContext(.NET Core+)间接清理。
- 不要试图修改CLR内置缓存,没有公开API支持,强行反射修改会导致程序不稳定。
内容的提问来源于stack exchange,提问作者Lucas Clandfield
相关产品推荐
相关产品推荐

