You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在更新依赖程序集时正确处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 13:28:22