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
相关产品推荐
相关产品推荐

