通用类型层次结构的流畅接口

Fluent Interface for Generic Type Hierarchy

我相信这是实现我想要的唯一方法,但我想把它放在那里看看是否有不需要使用 dynamic/reflection.

的解决方案

我有以下类型层次结构,只剩​​下最基本的部分来证明这一点:

// 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 的代码的实现方法?

C# 中的泛型是全有或全无。您要么全部通过,就像您已经完成的那样,要么 none。它的设计方式必须能够推断出所有论点。对于您正在做的事情,您可以只使用 Field<T> 而不是 TField<T>,删除该通用类型参数;尽管它可能不那么理想。还有其他方法...一些 FLUENT 设计 return 包含泛型作为属性的新类型允许您继续前进,但您的延续也需要使用该延续类型的逻辑。这有点令人困惑,但我觉得你明白了。如果不告诉我。

如果 where 约束也能帮助推断类型就好了,但事实并非如此。 Eric Lippert 最近帮助我了解到 C# 仅尝试推断通用参数,如果无法推断则失败。 where 约束只是将泛型类型限制为一个基类,并告知开发者。虽然感觉我们也可以基于约束进行推断,但由于我们是基于类型,所以 C# 不会。埃里克对不这样做有意见,我敢肯定这比我对 ATM 的理解要多。无论哪种方式,你都有它。

为了 "extendable" 流畅的界面,我们在 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 的附加类型参数,它将特定 return 类型的流畅方法委托给子项。