扩展方法和类型推断

本文关键字:类型 方法 扩展 | 更新日期: 2023-09-27 18:12:33

我正在尝试与扩展基描述符的许多泛型和描述符创建一个流畅的接口。我把它放在github的repo中,因为粘贴所有的代码会使它无法读取。

在阅读了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中隐藏ZooKeeper实现的"新"方法。不同之处在于ExperiencedZooKeeper中的new方法返回的是正确的类型。恐怕没有new方法是无法做到这一点的。

我尝试采取的另一种方法是将"setter"移动到扩展方法中。这对于。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;
}

这仍然是OK的,工作得很好,你仍然可以流利地调用它:

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)

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

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这个repo中的README实际上解释了我试图解决的实际问题。

所以问题真的是,有没有一种方法可以创建这种流畅的方法风格,而不是将ZooKeeper的每个方法添加到ZooKeeper的每个子类中,new隐藏ZooKeeper本身的方法?

扩展方法和类型推断

一种可能性是为每个级别创建一个基类,并从它派生一个空处理程序类:

基础类:

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
{
}

用法如你的问题所示。