具有嵌套通用约束的C#中的Fluent API设计

时间:2018-10-22 12:54:54

标签: c# api-design fluent

我目前正在实现内部使用的IoC设置API(极大地启发了Autofac的模块系统)。

我们有Module个可以通过强类型配置进行配置,并且 我希望一个模块能够需要其他模块,所以我可以有一个类似于“ composition-root”的主模块,它将引导整个应用程序。

public interface IModule<TConfig>
{
    TConfig Config { get; }

    void Load(ContainerBuilder builder);

    void LoadExtraModules(ModuleRegister register);
}

我目前正在设计ModuleRegister类。我想要做的就是这样:

public class MyModule : ModuleBase<ApplicationConfiguration>
{
    public void LoadExtraModules(ModuleRegister register)
    {
        register.Module<SqlModule>().WithConfig(new SqlConfiguration() { ... });
    }
}

public class SqlModule : ModuleBase<SqlConfiguration>
{
    public void Load(ContainerBuilder builder)
    {
         // configuration code.
    }
}

我想让Intellisense以某种方式建议SqlConfigurationSqlModule的正确配置类型,但我没有做到这一点:我想表达一个类似于

// ... inside an helper ExtraModulesRegister<TModule> class

public void WithConfig<TConfig>(TConfig configuration)
    where TModule : IModule<TConfig>
{
    ...
}

但是显然我只能表达对TConfig的约束,不能表达对TModule的约束。

我发现的唯一解决方案是使用扩展方法,如下所示:

    public static void WithConfig<TConfig, TModule>(this ExtraModulesRegister<TModule> register,
        TConfig configuration)
        where TModule : IModule<TConfig>, new()
    {
        register.LoadModule<TModule, TConfig>(configuration);
    }

所以我可以表达两种类型约束,其中一种是在已经定义的通用参数TModule上。

(几乎)我可以自由更改所有内容的设计。

任何建议都值得赞赏。

1 个答案:

答案 0 :(得分:1)

我尝试使用ExtraModulesRegister类本身的两个参数进行参数化:

public class ExtraModulesRegister<TModule, TConfig> wher TModule : IModule<TConfig> {

    void WithConfig(TConfig config) {

    }

}

但是现在您可能需要一些技巧让TConfig推断SqlConfig,所以您不需要传递两个参数。我想像辅助类型这样的东西可以帮助您,因此您调用register.Module(X<SqlModule>()),因此通过传递类似X<TModule>的参数,可以使Module()方法同时推断出TModuleTConfig

public ExtraModulesRegister<TModule, TConfig> Module<TModule, TConfig>(X<TModule> module) where TModule : IModule<TConfig> {
    ...
}

class X<T> {}

public static X<T> X<T>() {
    return new X<T>();
}

不幸的是,看起来C#无法推断类型。在Java中,相同的模式有效,并且编译器可以从一个参数和它们之间的定义关系推断两种类型。

编辑: 您可以在C#中使用它,但是语法可能不是,

public class ExtraModulesRegister<TConfig> {
    void WithConfig(TConfig config) {}
}

// Module method
public ExtraModulesRegister<TConfig> Module<TConfig>(IModule<TConfig> fakeModule) {
    Return new ExtraModulesRegister<TConfig>();
}

// Usage
register.Module(default(SqlModule)).WithConfig(new SqlConfig());