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

.NET 6中AssemblyLoadContext.Default与自定义LoadContext插件加载异常问题

问题描述

我有一个主控制台应用程序和一个作为DLL的插件,插件必须实现IPlugin接口,涉及三个项目:

  • 主控制台应用程序项目
  • 插件项目
  • 包含IPlugin接口的抽象项目,供前两个项目引用

插件被复制到主控制台应用程序下的一个文件夹中,IPlugin.dll同时存在于主程序目录和插件目录中。我通过自定义JobLoadContext类动态加载插件及其子目录中的依赖项,代码如下:

class JobLoadContext : AssemblyLoadContext
{
    private AssemblyDependencyResolver _resolver;

    public JobLoadContext(string pluginPath)
    {
        _resolver = new AssemblyDependencyResolver(pluginPath);
    }

    protected override Assembly? Load(AssemblyName assemblyName)
    {
        string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
        if (assemblyPath != null)
        {
            return LoadFromAssemblyPath(assemblyPath);
        }
        return null;
    }

    protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
    {
        string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
        if (libraryPath != null)
        {
            return LoadUnmanagedDllFromPath(libraryPath);
        }
        return IntPtr.Zero;
    }
}

在主程序中通过以下代码加载插件:

var loadContext = new JobLoadContext(path);
var asm = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(path)));

加载完成后,尝试查询实现IPlugin接口的类型:

var moduleInitializerType = asm.GetTypes().FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
//调用插件中实现IPlugin接口的Init()方法的其他代码

问题在于moduleInitializerType始终为null,但在监视窗口执行相同语句却能找到该类型。

推测原因是LoadContext的工作机制导致的冲突:主程序使用的是自身目录下的IPlugin.dll,而自定义LoadContext加载的插件引用的是插件目录下的IPlugin.dll,即使版本相同也会造成类型不匹配。若使用AssemblyLoadContext.Default加载插件,则无法加载插件目录中的部分依赖DLL;仅当将插件项目添加到主程序编译时,问题才会消失。

想知道是否有方法正确合并LoadContext,或者将自定义LoadContext的内容复制到包含主程序静态引用的AssemblyLoadContext.Default中?

解决方案

核心思路:共享抽象程序集

问题根源在于同一个IPlugin类型被加载到了两个不同的AssemblyLoadContext中,导致类型系统认为它们是不同的类型。解决的关键是让自定义JobLoadContext复用主程序(Default上下文)中已加载的IPlugin程序集,而不是重新加载插件目录下的副本。

修改JobLoadContext的Load方法,优先从Default上下文查找并加载抽象程序集:

class JobLoadContext : AssemblyLoadContext
{
    private AssemblyDependencyResolver _resolver;

    public JobLoadContext(string pluginPath) : base(isCollectible: false)
    {
        _resolver = new AssemblyDependencyResolver(pluginPath);
    }

    protected override Assembly? Load(AssemblyName assemblyName)
    {
        // 先检查Default上下文是否已加载该程序集,尤其是抽象接口所在的程序集
        var defaultAssembly = AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName);
        if (defaultAssembly != null)
        {
            return defaultAssembly;
        }

        // 再尝试从插件目录解析加载其他依赖
        string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
        if (assemblyPath != null)
        {
            return LoadFromAssemblyPath(assemblyPath);
        }
        return null;
    }

    protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
    {
        string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
        if (libraryPath != null)
        {
            return LoadUnmanagedDllFromPath(libraryPath);
        }
        return IntPtr.Zero;
    }
}

额外优化建议

  1. 清理插件目录中的重复抽象DLL:直接删除插件目录下的IPlugin.dll,确保插件编译时仅引用抽象项目,运行时依赖主程序提供的IPlugin.dll,从根源避免重复加载。
  2. 避免将自定义上下文内容复制到Default上下文:AssemblyLoadContext设计上是隔离的,强行合并会破坏隔离性,引发更多依赖冲突问题。正确的做法是通过共享核心抽象来打通上下文间的类型兼容。
  3. 验证类型匹配:修改后可以在代码中添加验证,确认插件中的类型与主程序的IPlugin类型属于同一个程序集:
var pluginType = asm.GetTypes().FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
if (pluginType != null)
{
    Console.WriteLine($"插件类型所属程序集:{pluginType.Assembly.FullName}");
    Console.WriteLine($"主程序IPlugin所属程序集:{typeof(IPlugin).Assembly.FullName}");
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 14:25:47