泛型插件引擎设计困境:寻求强类型替代dynamic的方案
插件引擎契约优化与强类型处理方案
针对你遇到的泛型插件契约、强类型缺失及设置动态更新的问题,以下是两种可落地的优化方案,既保留插件开发者的强类型体验,又解决主程序的编译时检查问题:
方案1:非泛型基础接口 + 泛型接口继承
通过定义非泛型核心契约,让泛型接口继承它,主程序依赖非泛型接口交互,插件开发者则使用强类型的泛型接口实现。
契约设计(contract.cs)
public interface ISettings { } // 主程序依赖的非泛型基础接口 public interface IPlugin { // 主程序用于动态更新设置的入口 void UpdateSettings(object settings); void Start(); // 可选:设置合法性校验 bool ValidateSettings(object settings, out string errorMessage); } // 插件开发者使用的泛型接口,继承非泛型接口 public interface IPlugin<TSettings> : IPlugin where TSettings : ISettings { // 强类型的当前设置 TSettings CurrentSettings { get; } // 强类型的设置更新方法 void UpdateSettings(TSettings settings); }
插件实现示例(plugin.cs)
public class MySettings : ISettings { public string ApiKey { get; set; } public int Timeout { get; set; } } public class MyPlugin : IPlugin<MySettings> { public MySettings CurrentSettings { get; private set; } // 构造函数可注入初始设置,后续可通过UpdateSettings修改 public MyPlugin(MySettings initialSettings = null) { CurrentSettings = initialSettings ?? new MySettings(); } // 适配非泛型接口的UpdateSettings,内部转强类型处理 void IPlugin.UpdateSettings(object settings) { if (settings is not MySettings typedSettings) throw new ArgumentException("Invalid settings type", nameof(settings)); UpdateSettings(typedSettings); } // 强类型设置更新,插件开发者直接使用 public void UpdateSettings(MySettings settings) { if (string.IsNullOrEmpty(settings.ApiKey)) throw new InvalidOperationException("ApiKey cannot be empty"); CurrentSettings = settings; } public void Start() { // 直接使用强类型设置,无需转换 Console.WriteLine($"Starting with ApiKey: {CurrentSettings.ApiKey}, Timeout: {CurrentSettings.Timeout}"); } // 适配非泛型接口的校验逻辑 bool IPlugin.ValidateSettings(object settings, out string errorMessage) { errorMessage = string.Empty; if (settings is not MySettings typedSettings) { errorMessage = "Settings must be MySettings type"; return false; } if (typedSettings.Timeout <= 0) { errorMessage = "Timeout must be positive"; return false; } return true; } }
主程序处理逻辑(main.cs)
主程序通过非泛型接口交互,无需dynamic,同时通过反射获取插件的泛型参数以构建设置:
// 加载插件程序集 var pluginAssembly = Assembly.LoadFrom("plugin.dll"); // 筛选所有IPlugin实现类 var pluginTypes = pluginAssembly.GetTypes() .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); foreach (var pluginType in pluginTypes) { // 1. 依赖注入容器创建实例(示例用Activator简化) var plugin = (IPlugin)Activator.CreateInstance(pluginType); // 2. 获取插件对应的TSettings类型 var genericInterface = pluginType.GetInterfaces() .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPlugin<>)); var settingsType = genericInterface.GetGenericArguments()[0]; // 3. 主程序构建设置对象(从配置/数据库读取) var settings = BuildSettings(settingsType); // 4. 校验设置合法性 if (!plugin.ValidateSettings(settings, out var error)) { Console.WriteLine($"Plugin {pluginType.Name} invalid settings: {error}"); continue; } // 5. 更新设置并启动插件 plugin.UpdateSettings(settings); plugin.Start(); // 后续动态修改设置 var updatedSettings = BuildUpdatedSettings(settingsType); plugin.UpdateSettings(updatedSettings); } // 反射构建设置对象示例 object BuildSettings(Type settingsType) { var settings = Activator.CreateInstance(settingsType); settingsType.GetProperty("ApiKey")?.SetValue(settings, "my-api-key-123"); settingsType.GetProperty("Timeout")?.SetValue(settings, 30); return settings; }
方案2:泛型方法封装 + 反射调用
如果不想引入非泛型接口,可在主程序中封装强类型的泛型处理方法,通过反射调用该方法处理每个插件,避免dynamic。
主程序核心逻辑
// 强类型处理方法,具备编译时检查 void ProcessPlugin<TSettings>(IPlugin<TSettings> plugin) where TSettings : ISettings { // 构建强类型设置 TSettings settings = BuildStronglyTypedSettings<TSettings>(); // 校验设置 if (!ValidatePluginSettings(plugin, settings, out var error)) { Console.WriteLine($"Invalid settings: {error}"); return; } // 更新设置并启动 plugin.UpdateSettings(settings); plugin.Start(); } // 扫描插件并调用泛型方法 var pluginAssembly = Assembly.LoadFrom("plugin.dll"); var pluginTypes = pluginAssembly.GetTypes() .Where(t => t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPlugin<>)) && !t.IsInterface && !t.IsAbstract); foreach (var pluginType in pluginTypes) { var genericInterface = pluginType.GetInterfaces() .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPlugin<>)); var settingsType = genericInterface.GetGenericArguments()[0]; // DI容器创建插件实例 var plugin = Activator.CreateInstance(pluginType); // 反射调用泛型处理方法 var processMethod = typeof(Program).GetMethod(nameof(ProcessPlugin), BindingFlags.NonPublic | BindingFlags.Instance); var genericProcessMethod = processMethod.MakeGenericMethod(settingsType); genericProcessMethod.Invoke(this, new[] { plugin }); } // 强类型设置构建示例 TSettings BuildStronglyTypedSettings<TSettings>() where TSettings : ISettings, new() { return new TSettings { ApiKey = "my-api-key-456", Timeout = 60 }; }
简化插件开发的抽象基类
可以提供抽象基类减少插件开发者的重复代码:
public abstract class PluginBase<TSettings> : IPlugin<TSettings> where TSettings : ISettings, new() { public TSettings CurrentSettings { get; protected set; } protected PluginBase(TSettings initialSettings = null) { CurrentSettings = initialSettings ?? new TSettings(); } void IPlugin.UpdateSettings(object settings) { if (settings is not TSettings typedSettings) throw new ArgumentException($"Expected {typeof(TSettings).Name}", nameof(settings)); UpdateSettings(typedSettings); } bool IPlugin.ValidateSettings(object settings, out string errorMessage) { errorMessage = string.Empty; if (settings is not TSettings typedSettings) { errorMessage = $"Invalid settings type"; return false; } return ValidateSettings(typedSettings, out errorMessage); } // 插件开发者只需实现以下强类型方法 public abstract void UpdateSettings(TSettings settings); public abstract void Start(); protected abstract bool ValidateSettings(TSettings settings, out string errorMessage); }
插件开发者继承该基类后,仅需实现业务相关的强类型方法即可。
内容的提问来源于stack exchange,提问作者user1858120
相关产品推荐
相关产品推荐

