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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 19:05:01