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

泛型插件引擎设计困境:寻求强类型替代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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 22:54:52