我正在尝试使用许多扩展基本描述符的泛型和描述符建立一个流畅的接口.
我把它放在一个
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中隐藏ZooKeeper实现的’新’方法.不同之处在于ExperiencedZooKeeper中的新方法返回正确的类型.没有新方法,AFAIK就没有办法做到这一点.
我试图采取的另一种方法是将’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;
}
你不能这样打电话给喜欢:
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
这个回购中的自述文件实际上解释了我试图解决的现实问题.
所以问题是,有没有办法创建这种流畅的方法风格,而不是将ZooKeeper的每个方法都添加到ZooKeeper的每个子类中,而新的隐藏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
{
}
用法就像你在问题中展示的一样.