通用类型层次结构的Fluent接口

时间:2018-12-08 14:36:13

标签: c# .net generics fluent

我相信这是实现我想要的唯一方法,但是我想把它放在那里看看是否有不需要使用动态/反射的解决方案。

我具有以下类型的层次结构,剥夺了基本知识以说明要点:

// Validators:

public abstract class Validator<T> { }

public class RequiredValidator<T> : Validator<T> { }

// Fields:

public abstract class Field { }

public abstract class Field<T> : Field
{
    public void AddValidator(Validator<T> validator) => 
        Console.WriteLine($"Added validator {validator.GetType()}");
}

public sealed class ValueField<T> : Field<T> { }
public sealed class ComputedField<T> : Field<T> { }
...many other field types that inherit Field<T>

这是我要实现的流畅接口的示例用法:

ValueField<string> field1 = new ValueField<string>().Required();

Required()方法必须在继承Field<T>的所有类型上都可用。

这是我想出的:

public static class Extensions
{
    public static TField Required<TField, T>(this TField field) where TField : Field<T>
    {
        field.AddValidator(new RequiredValidator<T>());
        return field;
    }

    public static TField DynamicRequired<TField>(this TField field) where TField : Field
    {
        DynamicAddRequiredValidator((dynamic)field);
        return field;
    }

    private static void DynamicAddRequiredValidator<T>(Field<T> field)
    {
        field.AddValidator(new RequiredValidator<T>());
    }
}

void Main()
{   
    // This is desired API usage but results in error:
    // The type arguments for method 'Extensions.Required<TField,T>(TField)' cannot be inferred from the usage.
    ValueField<string> field1 = new ValueField<string>().Required();

    // This works but the user shouldn't have to specify types like this, makes it very annoying to use:
    ValueField<string> field2 = new ValueField<string>().Required<ValueField<string>, string>();

    // This works but requires dynamic:
    ValueField<string> field3 = new ValueField<string>().DynamicRequired();
}

我是否缺少一种避免使用基于dynamic的代码的方法?

2 个答案:

答案 0 :(得分:2)

C#中的泛型是全有还是全无。您要么像过去那样全部通过,要么一无所有。必须以可以推断所有参数的方式进行设计。对于您正在执行的操作,可以仅使用Field<T>而不是TField<T>,删除该泛型类型参数。尽管可能不那么理想。还有其他方法...一些FLUENT设计返回包含泛型作为属性的新类型,使您可以继续前进,但是您的继续也需要使用该继续类型的逻辑。这有点令人困惑,但我觉得您理解。如果没有,请告诉我。

如果where约束还可以帮助推断类型,但事实并非如此,那就太好了。埃里克·利珀特(Eric Lippert)最近帮助我理解了C#仅尝试推断通用参数,如果无法推断出它,它将失败。 where约束仅用于将泛型类型限制为基本类型并通知开发人员。尽管确实可以基于约束来推断,但由于我们基于类型,因此C#不能。埃里克(Eric)对于不这样做有一种看法,我敢肯定我对ATM的理解不止于此。无论哪种方式,都可以。

答案 1 :(得分:0)

对于“可扩展”的流畅接口,我们在Java中使用以下技巧(如果可以,在C#中也可以尝试):

public class Field<L extends Field<L, V>, V> {
    public L required() {
        //...
        return (L) this;
    }
}

public class ValueField<V> extends Field<ValueField<V>, V> {
}

现在您可以拨打所需的电话了

ValueField<String> v = new ValueField<String>().required();

这要归功于Field的附加类型参数,该参数将流利方法的特定返回类型委托给子代。