.NET 6加载同名不同版本插件遇FileLoadException的解决方法
解决.NET 6中同名不同版本插件加载的FileLoadException问题
问题背景
在.NET 6应用中实现了多环境插件模型,配置定义了不同环境对应的插件路径及版本:
{ "Environments": [ { "EnvironmentName": "abc1", "PluginName": "plugin1\\abcplugin,myplugin" // 对应v1.0.0.0版本 }, { "EnvironmentName": "abc2", "PluginName": "plugin2\\abcplugin,myplugin" // 对应v2.0.0.0版本 } ] }
插件文件按版本隔离存放:
plugin1文件夹:包含abcplugin.dll(版本v1.0.0.0)plugin2文件夹:包含abcplugin.dll(版本v2.0.0.0)
遍历环境加载插件时,第二个插件执行Assembly.LoadFrom()操作时抛出异常:
System.IO.FileLoadException: Assembly with same name is already loaded
当前插件实例化代码如下:
public static ISyncPlugin Create(string assemblyName, string typeName, INotificationService notificationService) { var path = Assembly.GetExecutingAssembly().Location; // 获取当前运行程序集路径 path = path.Substring(0, path.LastIndexOf('\\') + 1); // 截取目录部分 path = path + Constants.PLUGIN_FOLDER + assemblyName + ".dll"; // 拼接插件完整路径 if (!File.Exists(path)) { notificationService.LogError($"SyncPluginFactory could not find plugin assembly at {path} - please check the spelling:"); return null; } var asm = Assembly.LoadFrom(path); if (assemblyName.Contains('\\')) { assemblyName = assemblyName.Substring(assemblyName.LastIndexOf('\\') + 1); // 移除路径中的文件夹名称 } var type = asm.GetType(assemblyName + "." + typeName); if (type == null) { notificationService.LogError($"SyncPluginFactory cannot find {assemblyName} {typeName}- please check the spelling"); return null; } ISyncPlugin plugin; try { plugin = (ISyncPlugin)Activator.CreateInstance(type); var fvi = FileVersionInfo.GetVersionInfo(path); notificationService.LogDebug($"Plugin {assemblyName}.{typeName} instantiated successfully - version {fvi.FileVersion}"); } catch (Exception e) { notificationService.LogError($"SyncPluginFactory cannot instantiate {assemblyName} {typeName}- please check the spelling: {e}"); return null; } return plugin; }
问题原因
CLR默认通过程序集的显示名称(包含名称、版本、公钥标记等元数据)识别程序集。当两个程序集文件名相同但版本不同时,Assembly.LoadFrom()会尝试将它们加载到同一个默认加载上下文,CLR认为这是重复加载,因此抛出FileLoadException。
解决方案
1. 使用Assembly.LoadFile()加载(简单但有局限)
Assembly.LoadFile()直接从指定路径加载程序集,忽略程序集的显示名称冲突,但存在以下限制:
- 不会自动加载程序集的依赖项,需手动处理依赖路径
- 不同加载的同名程序集无法共享类型,类型实例不能跨加载上下文转换
修改代码中加载程序集的行:
// 替换原有的Assembly.LoadFrom(path) var asm = Assembly.LoadFile(path);
2. 使用AssemblyLoadContext隔离加载(推荐)
.NET Core/.NET 5+引入的AssemblyLoadContext可以为每个插件创建独立的加载上下文,实现同名不同版本程序集的完全隔离,这是.NET 6中最可靠的方案。
修改后的完整代码:
using System.Runtime.Loader; using System.IO; public static ISyncPlugin Create(string assemblyName, string typeName, INotificationService notificationService) { var execPath = Assembly.GetExecutingAssembly().Location; var basePath = Path.GetDirectoryName(execPath); var fullPluginPath = Path.Combine(basePath, Constants.PLUGIN_FOLDER, assemblyName + ".dll"); if (!File.Exists(fullPluginPath)) { notificationService.LogError($"SyncPluginFactory could not find plugin assembly at {fullPluginPath} - please check the spelling"); return null; } // 为每个插件创建独立的可回收加载上下文 var pluginLoadContext = new AssemblyLoadContext($"{assemblyName}_LoadContext", isCollectible: true); Assembly asm; try { asm = pluginLoadContext.LoadFromAssemblyPath(fullPluginPath); } catch (Exception ex) { notificationService.LogError($"Failed to load plugin assembly: {ex.Message}"); pluginLoadContext.Unload(); return null; } var cleanedAssemblyName = assemblyName.Contains('\\') ? assemblyName.Substring(assemblyName.LastIndexOf('\\') + 1) : assemblyName; var pluginType = asm.GetType($"{cleanedAssemblyName}.{typeName}"); if (pluginType == null) { notificationService.LogError($"SyncPluginFactory cannot find type {cleanedAssemblyName}.{typeName}"); pluginLoadContext.Unload(); return null; } ISyncPlugin plugin; try { plugin = (ISyncPlugin)Activator.CreateInstance(pluginType); var versionInfo = FileVersionInfo.GetVersionInfo(fullPluginPath); notificationService.LogDebug($"Plugin {cleanedAssemblyName}.{typeName} instantiated successfully - version {versionInfo.FileVersion}"); } catch (Exception e) { notificationService.LogError($"SyncPluginFactory cannot instantiate plugin: {e.Message}"); pluginLoadContext.Unload(); return null; } // 注意:当插件不再使用时,可调用pluginLoadContext.Unload()释放资源 return plugin; }
3. 强签名程序集并区分版本(需插件配合)
如果插件程序集使用强签名,确保每个版本的程序集拥有不同的版本号和唯一的公钥标记,CLR会将它们视为完全不同的程序集,此时Assembly.LoadFrom()可以正常加载。但这种方式需要插件开发阶段同步更新版本信息,灵活性较低。
内容的提问来源于stack exchange,提问作者Rick Hodder
相关产品推荐
相关产品推荐

