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

.NET Framework 4.8运行时切换不同版本DLL依赖及注册问题

问题根因
  • .NET Framework默认应用域(AppDomain)中程序集一旦加载就无法单独卸载,首次加载V1实现时,其依赖的Some.dll 1.0.0.0已经被载入当前AppDomain,后续加载V2时CLR检测到同程序集不同版本的冲突就会抛出异常。
  • Autofac容器构建完成后默认是不可变的,无法直接移除已注册的服务实现,重复注册同接口类型也会触发注册冲突。
可行解决方案

方案1:子AppDomain隔离(推荐,可完全卸载旧版本依赖)

这是.NET Framework下实现插件热切换的标准方案,通过独立子AppDomain加载插件,切换时直接销毁整个子AppDomain即可清理所有关联的程序集和依赖。

实现步骤:

  1. 给共享的IExchange接口添加跨域调用支持:
public interface IExchange : IMarshalByRefObject
{
    void DoSomething();
}
  1. 封装插件加载管理类:
public class ExchangePluginManager : IDisposable
{
    private AppDomain _pluginDomain;
    private IExchange _exchangeImpl;

    public IExchange Current => _exchangeImpl;

    public void LoadPlugin(string pluginPathPattern)
    {
        // 先卸载旧的子AppDomain
        UnloadPlugin();

        // 创建新的子AppDomain
        var setup = new AppDomainSetup
        {
            ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
            PrivateBinPath = Path.GetDirectoryName(pluginPathPattern)
        };
        _pluginDomain = AppDomain.CreateDomain($"ExchangePluginDomain_{Guid.NewGuid()}", null, setup);

        // 从子域加载Catalog并获取IExchange实例
        var catalogType = typeof(DirectoryCatalog);
        var catalog = (DirectoryCatalog)_pluginDomain.CreateInstanceFromAndUnwrap(
            catalogType.Assembly.Location,
            catalogType.FullName,
            false,
            BindingFlags.CreateInstance,
            null,
            new object[] { ".", pluginPathPattern },
            null,
            null
        );

        // 子域内构建Autofac容器并获取实现
        var containerBuilderType = typeof(ContainerBuilder);
        var builder = (ContainerBuilder)_pluginDomain.CreateInstanceFromAndUnwrap(
            containerBuilderType.Assembly.Location,
            containerBuilderType.FullName
        );
        builder.RegisterComposablePartCatalog(catalog);
        var container = builder.Build();
        _exchangeImpl = container.Resolve<IExchange>();
    }

    public void UnloadPlugin()
    {
        if (_pluginDomain != null)
        {
            AppDomain.Unload(_pluginDomain);
            _pluginDomain = null;
            _exchangeImpl = null;
        }
    }

    public void Dispose()
    {
        UnloadPlugin();
    }
}
  1. 切换版本直接调用LoadPlugin即可:
// 初始化加载V1
var pluginManager = new ExchangePluginManager();
pluginManager.LoadPlugin("*V1*.dll");

// 需要切换到V2时直接调用
pluginManager.LoadPlugin("*V2*.dll");

方案2:键控注册(适合不需要卸载程序集的场景)

如果可以接受两个版本的程序集同时驻留内存,可以通过配置程序集版本共存+Autofac键控注册实现切换,无需改动AppDomain。

实现步骤:

  1. 在app.config中配置Some.dll的版本共存规则,将两个版本的dll分别放在Plugins/V1和Plugins/V2目录:
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Some" publicKeyToken="xxxxxxx" culture="neutral" />
        <codeBase version="1.0.0.0" href="Plugins/V1/Some.dll" />
        <codeBase version="2.0.0.0" href="Plugins/V2/Some.dll" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>
  1. 启动时一次性注册两个版本的实现,用Key区分:
var catalogV1 = new DirectoryCatalog("./Plugins/V1", "*V1*.dll");
var catalogV2 = new DirectoryCatalog("./Plugins/V2", "*V2*.dll");

var builder = new ContainerBuilder();
builder.RegisterComposablePartCatalog(catalogV1).Keyed<IExchange>("V1");
builder.RegisterComposablePartCatalog(catalogV2).Keyed<IExchange>("V2");
// 注册默认实现
builder.Register(c => c.ResolveKeyed<IExchange>("V1")).As<IExchange>();
var container = builder.Build();
  1. 切换版本时通过IIndex获取对应实现即可:
// 需要切换到V2时,从IIndex中获取对应版本的实例
var exchangeImpl = container.Resolve<IIndex<string, IExchange>>()["V2"];
注意事项
  • 采用子AppDomain方案时,所有跨域传递的参数和返回值都必须支持序列化或者继承自MarshalByRefObject,否则会触发跨域调用异常。
  • 采用键控注册方案时,需要确保Some.dll的两个版本没有强命名冲突,且业务逻辑中不会同时调用两个版本的实现产生逻辑冲突。

内容的提问来源于stack exchange,提问作者Febertson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 14:54:04