.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即可清理所有关联的程序集和依赖。
实现步骤:
- 给共享的
IExchange接口添加跨域调用支持:
public interface IExchange : IMarshalByRefObject { void DoSomething(); }
- 封装插件加载管理类:
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(); } }
- 切换版本直接调用LoadPlugin即可:
// 初始化加载V1 var pluginManager = new ExchangePluginManager(); pluginManager.LoadPlugin("*V1*.dll"); // 需要切换到V2时直接调用 pluginManager.LoadPlugin("*V2*.dll");
方案2:键控注册(适合不需要卸载程序集的场景)
如果可以接受两个版本的程序集同时驻留内存,可以通过配置程序集版本共存+Autofac键控注册实现切换,无需改动AppDomain。
实现步骤:
- 在
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>
- 启动时一次性注册两个版本的实现,用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();
- 切换版本时通过IIndex获取对应实现即可:
// 需要切换到V2时,从IIndex中获取对应版本的实例 var exchangeImpl = container.Resolve<IIndex<string, IExchange>>()["V2"];
注意事项
- 采用子AppDomain方案时,所有跨域传递的参数和返回值都必须支持序列化或者继承自
MarshalByRefObject,否则会触发跨域调用异常。 - 采用键控注册方案时,需要确保
Some.dll的两个版本没有强命名冲突,且业务逻辑中不会同时调用两个版本的实现产生逻辑冲突。
内容的提问来源于stack exchange,提问作者Febertson
相关产品推荐
相关产品推荐

