2013-05-08 82 views
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隐藏实现ZooKeeper。区别在于ExperiencedZooKeeper中的new方法返回正确的类型。没有new方法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动物)不能正常工作,它需要一个额外的类型参数:

// 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,但是,这种情况下要稍微复杂一些,因为我们已经有一个类型参数描述我们需要的T的TZookKeeper。但是,像埃里克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的每个子类的方法,new隐藏ZooKeeper本身的方法?

+0

我之前忘记了给Favorite的调用,看看Program5.cs中有两个不同的调用,一个不编译,一个绝对不流利! – Jaap 2013-05-08 18:11:05

回答

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

用法就像你在你的问题中展示的一样。

+0

这就是所谓的“好奇地重复发生的模板”模式,我同意有时候它是要走的路,特别是当你的类的消费者主要处理具体类型并且基类型将被隐藏的很好时。不过,我不得不怀疑是否值得有一个抽象的'ExperiencedZooKeeperBase <,>'类。 Could not'ExperiencedZooKeeper <,>'直接来自'ZooKeeperBase <,>'? – 2013-05-08 14:20:10

+0

@JeremyTodd:在这个具体的例子中,这是可能的。但是,只要有另一层继承需要基类。 – 2013-05-08 14:22:59

+0

啊,我明白了。由于您的设置旨在为具体类只需要一个类型参数,因此您是对的。 – 2013-05-08 14:32:21