扩展方法和类型推断

4
我正在尝试创建一个具有许多泛型和描述符的流畅接口,这些描述符扩展了基本描述符。我将其放在 Github 存储库中,因为将所有代码粘贴到此处会使其难以阅读。
在阅读了 Eric Lippert 的类型约束文章(http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx)和阅读了 No type inference with generic extension method 后,我对该主题有了更好的理解,但仍有疑问。
假设您有一些允许流畅调用的类:
var giraffe = new Giraffe();
new ZooKeeper<Giraffe>()
    .Name("Jaap")
    .FeedAnimal(giraffe);

var reptile = new Reptile();
new ExperiencedZooKeeper<Reptile>()
    .Name("Martijn")
    .FeedAnimal(reptile)
    .CureAnimal(reptile);

类的样子如下:
public class ZooKeeper<T>
    where T : Animal
{
    internal string name;
    internal List<T> animalsFed = new List<T>();

    // this method needs to be fluent
    public ZooKeeper<T> Name(string name)
    {
        this.name = name;
        return this;
    }

    // this method needs to be fluent
    public ZooKeeper<T> FeedAnimal(T animal)
    {
        animalsFed.Add(animal);
        return this;
    }
}

public class ExperiencedZooKeeper<T> : ZooKeeper<T>
    where T : Animal
{
    internal List<T> animalsCured = new List<T>();

    // this method needs to be fluent
    // but we must new it in order to be able to call CureAnimal after this
    public new ExperiencedZooKeeper<T> Name(string name)
    {
        base.Name(name);
        return this;
    }

    // this method needs to be fluent
    // but we must new it in order to be able to call CureAnimal after this
    public new ExperiencedZooKeeper<T> FeedAnimal(T animal)
    {
        base.FeedAnimal(animal);
        return this;
    }

    // this method needs to be fluent
    public ExperiencedZooKeeper<T> CureAnimal(T animal)
    {
        animalsCured.Add(animal);
        return this;
    }
}

我试图消除ExperiencedZooKeeper中的“new”方法,以隐藏ZooKeeper的实现。不同之处在于ExperiencedZooKeeper中的“new”方法返回正确的类型。据我所知,没有办法在没有new方法的情况下做到这一点。
我尝试采取的另一种方法是将“setters”移到扩展方法中。这对于.Name()方法很有效,但它引入了一个包含内部字段的ZooKeeperBase
public abstract class ZooKeeperBase
{
    internal string name;

}

public class ZooKeeper<T> : ZooKeeperBase
    where T : Animal
{
    internal List<T> animalsFed = new List<T>();


    // this method needs to be fluent
    public ZooKeeper<T> FeedAnimal(T animal)
    {
        animalsFed.Add(animal);
        return this;
    }
}

public static class ZooKeeperExtensions
{

    // this method needs to be fluent
    public static TZooKeeper Name<TZooKeeper>(this TZooKeeper zooKeeper, string name)
        where TZooKeeper : ZooKeeperBase
    {
        zooKeeper.name = name;
        return zooKeeper;
    }
}

但是这种精确的方法对于FeedAnimal(T animal)并不起作用,它需要一个额外的类型参数:
// this method needs to be fluent
public static TZooKeeper FeedAnimal<TZooKeeper, T>(this TZooKeeper zooKeeper, T animal)
    where TZooKeeper : ZooKeeper<T>
    where T : Animal
{
    zooKeeper.animalsFed.Add(animal);
    return zooKeeper;
}

这仍然是可以的,而且运行良好,您仍然可以流畅地调用它:
new ExperiencedZooKeeper<Reptile>()
    .Name("Martijn")
    .FeedAnimal(reptile)
    .CureAnimal(reptile);

当我试图使以下方法流畅时,真正的问题开始出现:

public static TZooKeeper Favorite<TZooKeeper, T>(this TZooKeeper zooKeeper, Func<T, bool> animalSelector)
    where TZooKeeper : ZooKeeper<T>
    where T : Animal
{
    zooKeeper.favoriteAnimal = zooKeeper.animalsFed.FirstOrDefault(animalSelector);
    return zooKeeper;
}

您不能这样调用Favorite
new ExperiencedZooKeeper<Reptile>()
  .Name("Eric")
  .FeedAnimal(reptile)
  .FeedAnimal(new Reptile())
  .Favorite(r => r == reptile)

因为这会导致与泛型扩展方法无类型推断相同的问题,但是这种情况稍微复杂一些,因为我们已经有了一个描述所需T的Type参数TZookKeeper。但是像Eric Lipperts博客文章所述,类型约束不是签名的一部分:

The type arguments for method 'TestTypeInference5.ZooKeeperExtensions.Favorite<TZooKeeper,T>(TZooKeeper, System.Func<T,bool>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

完整的代码,请参考https://github.com/q42jaap/TestTypeInference。 这个存储库中的README实际上解释了我试图解决的现实问题。

所以问题真正在于,是否有一种方法可以创建这种流畅的方法风格,而不必向ZooKeeper的每个子类添加每个ZooKeeper方法,并使用new隐藏ZooKeeper本身的方法?


我之前忘记调用Favorite了,请查看Program5.cs文件,其中有两个不同的调用,一个无法编译,另一个明显不流畅! - Jaap
1个回答

2

有一种可能是为每个级别创建一个基类和一个派生自它的空处理程序类:

基类:

public abstract class ZooKeeperBase<TZooKeeper, TAnimal>
    where TZooKeeper : ZooKeeperBase<TZooKeeper, TAnimal>
    where TAnimal : Animal
{
    private string name;
    private List<TAnimal> animalsFed = new List<TAnimal>();
    private TAnimal favoriteAnimal;

    public TZooKeeper Name(string name)
    {
        this.name = name;
        return (TZooKeeper)this;
    }

    public TZooKeeper FeedAnimal(TAnimal animal)
    {
        animalsFed.Add(animal);
        return (TZooKeeper)this;
    }

    public TZooKeeper Favorite(Func<TAnimal, bool> animalSelector)
    {
        favoriteAnimal = animalsFed.FirstOrDefault(animalSelector);
        return (TZooKeeper)this;
    }
}

public abstract class ExperiencedZooKeeperBase<TZooKeeper, TAnimal>
    : ZooKeeperBase<TZooKeeper, TAnimal>
    where TZooKeeper : ExperiencedZooKeeperBase<TZooKeeper, TAnimal>
    where TAnimal : Animal
{
    private List<TAnimal> animalsCured = new List<TAnimal>();

    public TZooKeeper CureAnimal(TAnimal animal)
    {
        animalsCured.Add(animal);
        return (TZooKeeper)this;
    }
}

处理程序类:

public class ZooKeeper<T> : ZooKeeperBase<ZooKeeper<T>, T>
    where T : Animal
{
}

public class ExperiencedZooKeeper<T>
    : ExperiencedZooKeeperBase<ExperiencedZooKeeper<T>, T>
    where T : Animal
{
}

使用方式与您在问题中展示的一样。


这被称为“奇妙的递归模板”模式,我同意有时候这是可行的方式,特别是当你的类的消费者大多数将处理具体类型而基本类型将被隐藏得很好时。但我不禁想知道是否值得拥有一个抽象的ExperiencedZooKeeperBase<,>类。难道ExperiencedZooKeeper<,>不能直接从ZooKeeperBase<,>派生吗? - Jeremy Todd
@JeremyTodd:在这个具体的实例中,这是可能的。然而,一旦有另一层继承,那么基类就是必需的。 - Daniel Hilgarth
啊,我明白了。既然你的设置只需要一个类型参数来确定具体类,那么你是正确的。 - Jeremy Todd
谢谢你,丹尼尔,你对我在问题结尾描述的"Favorite call"有什么想法吗? - Jaap

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接