【问题标题】:Extension methods and type inference扩展方法和类型推断
【发布时间】:2013-05-08 13:55:29
【问题描述】:

我正在尝试使用许多扩展基本描述符的泛型和描述符创建一个流畅的接口。 我把它放在了一个 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 方法返回正确的类型。 AFAIK 如果没有 new 方法,就无法做到这一点。

我尝试采用的另一种方法是将“设置器”移动到扩展方法。这适用于 .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)

因为它会导致和No type inference with generic extension method一样的问题,但是这种情况稍微复杂一些,因为我们已经有了一个Type参数TZookKeeper来描述我们需要的T。但就像 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 这个 repo 中的自述文件实际上解释了我试图解决的现实问题。

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

【问题讨论】:

  • 我忘记了之前对Favorite的调用,看看Program5.cs,其中有两个不同的调用,一个不能编译,一个肯定不流畅!

标签: c# extension-methods type-inference


【解决方案1】:

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

基类:

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&lt;,&gt; 类。 ExperiencedZooKeeper&lt;,&gt; 不能直接从 ZooKeeperBase&lt;,&gt; 派生吗?
  • @JeremyTodd:在这个具体的例子中,这是可能的。但是,只要有另一层继承,就需要该基类。
  • 啊,我明白了。由于您的设置被设计为只需要一个具体类的类型参数,所以您是对的。
  • 谢谢丹尼尔,您对我在问题末尾描述的“收藏夹”电话有什么想法吗?
猜你喜欢
  • 2021-11-19
  • 2011-11-15
  • 1970-01-01
  • 1970-01-01
  • 2018-02-22
  • 1970-01-01
  • 1970-01-01
  • 2022-09-23
  • 1970-01-01
相关资源
最近更新 更多