I am trying to make a free interface with a lot of generics and descriptors that extend the base descriptors. I put this in the github repository because pasting all the code here will make it unreadable.
After reading Eric Lippert's post about type restrictions ( http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx ) and reading No type with universal extension method . I understood the subject a little better, but I still have questions.
Suppose you have several classes that allow you to call freely:
var giraffe = new Giraffe(); new ZooKeeper<Giraffe>() .Name("Jaap") .FeedAnimal(giraffe); var reptile = new Reptile(); new ExperiencedZooKeeper<Reptile>() .Name("Martijn") .FeedAnimal(reptile) .CureAnimal(reptile);
The classes are as follows:
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; } }
I tried to get rid of the "new" methods in ExperiencedZooKeeper , hiding the implementation of ZooKeeper . The difference is that the new methods in ExperiencedZooKeeper return the correct type. AFAIK there is no way to do this without the new methods.
Another approach I tried to do was to move the "setters" into extension methods. This works well for the .Name () method, but it introduces ZooKeeperBase , which contains an internal field:
public abstract class ZooKeeperBase { internal string name; } public class ZooKeeper<T> : ZooKeeperBase where T : Animal { internal List<T> animalsFed = new List<T>();
But this exact approach does not work for FeedAnimal (T animal), it needs an additional parameter of the type:
// 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; }
This is still good and works well, and you can still call it freely:
new ExperiencedZooKeeper<Reptile>() .Name("Martijn") .FeedAnimal(reptile) .CureAnimal(reptile);
The real problems begin when I try to speak fluently about the following method:
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; }
You cannot call Favorite as follows:
new ExperiencedZooKeeper<Reptile>() .Name("Eric") .FeedAnimal(reptile) .FeedAnimal(new Reptile()) .Favorite(r => r == reptile)
because it will lead to the same problem as No type inference with a common extension method , however this case is a bit more complicated, because we already have a parameter of type TZookKeeper that describes the T we need. But, like the Eric Lipperts blog post, type restrictions are not part of the signature:
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.
The full code can be found at https://github.com/q42jaap/TestTypeInference README in this repo actually explains the real life problem that I was trying to solve.
So the question is, is there a way to create this free method style without adding each ZooKeeper method to any subclass of ZooKeeper with new , hiding the method of ZooKeeper itself?