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

使用AssemblyLoadContext加载插件时遇ReflectionTypeLoadException问题

问题描述

我用AssemblyLoadContext在运行时加载多个插件程序集,遇到以下问题:加载使用主程序(此处为单元测试)已加载接口的插件时,触发ReflectionTypeLoadException。接口版本完全一致,文件二进制100%匹配,仅加载路径不同。强制让PlugInLoadContext从插件目录重新加载接口能解决问题,但这种方式有其他限制且不规范。

为复现问题,我制作了最小化项目,需创建4个.NET7项目:

环境搭建

  1. 共享类库SharedLib:定义单个接口
namespace SharedLib;
public interface ISharedInterface { }
  1. 接口类库InterfaceLib:引用SharedLib,定义接口
using SharedLib;

namespace InterfaceLib;
public interface IInterface
{
    Task Action(ISharedInterface shared);
}
  1. 用户类库ClassLib:引用上述两个项目
using InterfaceLib;
using SharedLib;

namespace ClassLib;
public class Implementation : IInterface
{ 
    public Task Action(ISharedInterface shared) => 
      throw new NotImplementedException(); 
}
// 移除该类则不会触发异常
public class SharedImplementation : ISharedInterface { }
  1. 主程序Program:仅引用InterfaceLib,注意必须保留IInterface引用才能复现问题
// 注释该行则不会触发异常!
InterfaceLib.IInterface _;

var pluginPath = Path.GetFullPath(AppContext.BaseDirectory + @"..\\..\\..\\..\\ClassLib\\bin\\Debug\\net7.0\\ClassLib.dll");

var context = new PlugInLoadContext();
context.AddPlugIn(pluginPath);

// 此处触发异常
var types = context.PlugIns.SelectMany(suba => suba.GetTypes().ToArray()).ToArray();

PlugInLoadContext实现代码

public class PlugInLoadContext : AssemblyLoadContext
{
    private readonly List<AssemblyDependencyResolver> _resolvers = new List<AssemblyDependencyResolver>();
    private readonly Dictionary<string, Assembly> _knownPlugIns = new Dictionary<string, Assembly>();

    public IEnumerable<Assembly> PlugIns => _knownPlugIns.Select(a => a.Value);

    public Assembly AddPlugIn(string plugInPath)
    {
        if (_knownPlugIns.TryGetValue(plugInPath, out var assembly)) { return assembly; }

        var result = Load(AssemblyName.GetAssemblyName(plugInPath));

        if (result == null)
        {
            _resolvers.Add(new AssemblyDependencyResolver(plugInPath));
            result = LoadFromAssemblyPath(plugInPath);
        }

        _knownPlugIns.Add(plugInPath, result);

        return result;
    }

    protected override Assembly? Load(AssemblyName assemblyName)
    {
        var result = LoadInternal(assemblyName);
        if (result != null) { Console.WriteLine($"{assemblyName.Name} loaded from {result.Location}"); }
        return result;
    }

    protected Assembly? LoadInternal(AssemblyName assemblyName)
    {
        var knownAssembly = Default.Assemblies.FirstOrDefault(a => a.FullName == assemblyName.FullName);
        if (knownAssembly != null) { return knownAssembly; }

        var assemblyPath = _resolvers.Select(r => r.ResolveAssemblyToPath(assemblyName)).Where(p => p != null).FirstOrDefault();
        if (assemblyPath != null) { return LoadFromAssemblyPath(assemblyPath); }

        return null;
    }
}

运行输出

System.Runtime loaded from C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.11\System.Runtime.dll
SharedLib loaded from <userpath>\ReflectionTypeLoadException\ClassLib\bin\Debug\net7.0\SharedLib.dll
InterfaceLib loaded from <userpath>\ReflectionTypeLoadException\Program\bin\Debug\net7.0\InterfaceLib.dll
Unhandled exception. System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types.
Method 'Action' in type 'ClassLib.Implementation' from assembly 'ClassLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
   at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
   at System.Reflection.Assembly.GetTypes()
   at Program.<<Main>$>b__0_0(Assembly suba) in <userpath>\ReflectionTypeLoadException\Program\Program.cs:line 17
   at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.ToArray()
   at Program.<Main>$(String[] args) in <userpath>\ReflectionTypeLoadException\Program\Program.cs:line 17
System.TypeLoadException: Method 'Action' in type 'ClassLib.Implementation' from assembly 'ClassLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
原因分析

问题核心是**.NET中类型的唯一性由「加载上下文+程序集+类型全名」共同决定**——即便程序集二进制完全一致,只要加载上下文不同,对应的类型就会被视为不同类型。

具体到你的场景:

  1. 主程序(Default加载上下文)提前加载了InterfaceLib,其依赖的SharedLib也被加载到Default上下文。
  2. 加载ClassLib时,由于ClassLib包含实现ISharedInterface的SharedImplementation,PlugInLoadContext会先尝试加载SharedLib——此时Default上下文的SharedLib尚未被加载(主程序未直接引用),因此会从插件目录加载SharedLib到PlugInLoadContext。
  3. 随后加载InterfaceLib时,Default上下文已存在该程序集,直接复用Default上下文的InterfaceLib。
  4. 矛盾由此产生:InterfaceLib中IInterface.Action的参数是Default上下文的ISharedInterface,但ClassLib.Implementation实现的Action参数是PlugInLoadContext上下文的ISharedInterface——CLR判定两个方法签名不匹配,认为Implementation未实现IInterface的Action方法,抛出异常。
解决方案

1. 统一共享程序集的加载路径

修改PlugInLoadContext.LoadInternal方法,优先从主程序目录加载共享程序集(如SharedLib、InterfaceLib),确保同一共享程序集仅在一个上下文加载:

protected Assembly? LoadInternal(AssemblyName assemblyName)
{
    // 先检查主程序目录是否存在目标程序集
    var appAssemblyPath = Path.Combine(AppContext.BaseDirectory, $"{assemblyName.Name}.dll");
    if (File.Exists(appAssemblyPath))
    {
        return LoadFromAssemblyPath(appAssemblyPath);
    }

    // 再复用Default上下文已加载的程序集
    var knownAssembly = Default.Assemblies.FirstOrDefault(a => a.FullName == assemblyName.FullName);
    if (knownAssembly != null) { return knownAssembly; }

    // 最后从插件目录加载依赖
    var assemblyPath = _resolvers.Select(r => r.ResolveAssemblyToPath(assemblyName)).Where(p => p != null).FirstOrDefault();
    if (assemblyPath != null) { return LoadFromAssemblyPath(assemblyPath); }

    return null;
}

此方式会让SharedLib从主程序目录加载到PlugInLoadContext,与InterfaceLib依赖的SharedLib类型保持一致,避免类型不匹配。

2. 给共享程序集添加强命名

给SharedLib和InterfaceLib签名强命名后,CLR会将相同强命名的程序集视为同一个(只要版本匹配),不受加载路径和上下文影响。步骤:

  • 在项目属性中启用「签名」,创建强命名密钥文件。
  • 重新编译所有引用这些程序集的项目。
    此方式从根源上解决了同一程序集多上下文加载的类型冲突问题。

3. 完全隔离插件上下文(不推荐,除非必要)

让PlugInLoadContext不复用Default上下文的任何程序集,所有依赖都从插件目录加载。修改LoadInternal方法,移除对Default上下文程序集的复用:

protected Assembly? LoadInternal(AssemblyName assemblyName)
{
    // 仅从插件目录加载依赖
    var assemblyPath = _resolvers.Select(r => r.ResolveAssemblyToPath(assemblyName)).Where(p => p != null).FirstOrDefault();
    if (assemblyPath != null) { return LoadFromAssemblyPath(assemblyPath); }

    // 系统程序集交给基类处理
    return null;
}

但此方式会导致同一程序集被多次加载,增加内存开销,不符合共享程序集的设计初衷,仅适用于完全隔离的插件场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 09:41:01