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

C#反射实现插件动态加载:跨程序集依赖问题求解

解决插件间跨程序集依赖的动态加载问题

1. 注册程序集解析事件,手动加载依赖

CLR抛出FileNotFoundException是因为无法自动定位依赖程序集,你可以通过注册程序集解析事件,在CLR找不到目标程序集时手动指定路径加载:

public class Program
{
    public static void Main()
    {
        // 注册程序集解析事件
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

        var assemblyA = Assembly.LoadFrom("pathToA.dll");
        var classNameA = assemblyA.GetExportedTypes().FirstOrDefault(x => x.BaseType == typeof(PluginController));
        
        var constructorA = classNameA.GetConstructors()[0];
        var parameterInfoA = constructorA.GetParameters();
        // 后续实例化逻辑...
    }

    private static Assembly? CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        var assemblyName = new AssemblyName(args.Name);
        // 插件及依赖DLL的统一目录
        var pluginDirectory = Path.GetDirectoryName("pathToA.dll");
        var assemblyPath = Path.Combine(pluginDirectory, $"{assemblyName.Name}.dll");

        if (File.Exists(assemblyPath))
        {
            return Assembly.LoadFrom(assemblyPath);
        }
        return null;
    }
}

如果是.NET Core/.NET 5+环境,推荐使用AssemblyLoadContext替代AppDomain解析事件,隔离性和安全性更好:

var pluginLoadContext = new AssemblyLoadContext("PluginLoadContext", isCollectible: true);
pluginLoadContext.Resolving += (context, assemblyName) =>
{
    var pluginDirectory = Path.GetDirectoryName("pathToA.dll");
    var assemblyPath = Path.Combine(pluginDirectory, $"{assemblyName.Name}.dll");
    if (File.Exists(assemblyPath))
    {
        return context.LoadFromAssemblyPath(assemblyPath);
    }
    return null;
};

var assemblyA = pluginLoadContext.LoadFromAssemblyPath("pathToA.dll");

2. 提前加载依赖程序集

在加载主插件(A.dll)之前,先手动加载它依赖的所有程序集(B.dll),让CLR在解析A.dll类型时能直接找到已加载的依赖类型:

public static void Main()
{
    // 先加载依赖的B.dll
    Assembly.LoadFrom("pathToB.dll");
    // 再加载A.dll
    var assemblyA = Assembly.LoadFrom("pathToA.dll");
    
    var classNameA = assemblyA.GetExportedTypes().FirstOrDefault(x => x.BaseType == typeof(PluginController));
    var constructorA = classNameA.GetConstructors()[0];
    var parameterInfoA = constructorA.GetParameters();
    
    // 实例化时传入已创建的PluginB实例
    var pluginB = Activator.CreateInstance(Assembly.LoadFrom("pathToB.dll").GetExportedTypes().First(x => x.BaseType == typeof(PluginController)));
    var pluginA = constructorA.Invoke(new[] { pluginB });
}

3. 用依赖注入容器自动处理依赖解析

手动反射处理依赖容易出错,推荐使用DI容器(如Microsoft.Extensions.DependencyInjection)管理插件注册与实例化,容器会自动解析构造函数依赖:

public static void Main()
{
    var services = new ServiceCollection();

    // 加载所有插件程序集并注册为服务
    var pluginDirectory = "pathToPlugins";
    foreach (var dllPath in Directory.GetFiles(pluginDirectory, "*.dll"))
    {
        var assembly = Assembly.LoadFrom(dllPath);
        foreach (var type in assembly.GetExportedTypes().Where(t => t.BaseType == typeof(PluginController)))
        {
            services.AddTransient(type);
            // 如果插件实现了特定接口,同时注册接口与实现类
            var pluginInterface = type.GetInterfaces().FirstOrDefault(i => i.Name.StartsWith("IPlugin"));
            if (pluginInterface != null)
            {
                services.AddTransient(pluginInterface, type);
            }
        }
    }

    var serviceProvider = services.BuildServiceProvider();
    // 获取PluginA实例,容器自动注入PluginB依赖
    var pluginA = serviceProvider.GetService(typeof(PluginA));
}

4. 规避程序集合并的类型冲突问题

你之前合并DLL出现类型不匹配,是因为同一类型被合并到不同程序集后,CLR会将其视为不同类型。如果必须合并,要确保所有依赖插件都合并到同一个程序集,或使用ILMerge等工具时保持类型唯一性;更稳妥的方式是放弃合并,采用前面的程序集解析或提前加载方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 04:22:37