4

I'm trying to make a fluent interface with lots of generics and descriptors that extend base descriptors. I've put this in a github repo because pasting all of the code here would make it unreadable.

After having read Eric Lippert's post about type constraints (http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx) and reading No type inference with generic extension method I understood the subject a little bit better, but I still got questions.

Suppose you have some classes that allow fluent calls:

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 look like this:

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 new methods.

Another approach I tried to take is to move the 'setters' to extension methods. This works well for the .Name() method, but it introduces a ZooKeeperBase which contains the internal field:

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

But this exact approach doesn't work for FeedAnimal(T animal), it needs an extra type parameter :

// 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 OK and works well and you can still call it fluently:

new ExperiencedZooKeeper<Reptile>()
    .Name("Martijn")
    .FeedAnimal(reptile)
    .CureAnimal(reptile);

The real problems start when I try to make the following method fluent:

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 like this:

new ExperiencedZooKeeper<Reptile>()
  .Name("Eric")
  .FeedAnimal(reptile)
  .FeedAnimal(new Reptile())
  .Favorite(r => r == reptile)

because it will result in the same problem as No type inference with generic extension method, however, this case is slightly more complicated, because we already have a Type parameter TZookKeeper which describes the T we need. But like Eric Lipperts blog post, the type constraints 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.

For the full code, please refer to https://github.com/q42jaap/TestTypeInference The README in this repo actually explains the real life problem I tried to solve.

So the question really is, is there a way of creating this fluent method style without adding every method of ZooKeeper to every subclass of ZooKeeper with new hiding the method of ZooKeeper itself?

Community
  • 1
  • 1
Jaap
  • 3,081
  • 2
  • 29
  • 50
  • I forgot the call to Favorite earlier, have a look at Program5.cs where there are two different calls, one that doesn't compile and one that's definitely not fluent! – Jaap May 08 '13 at 18:11

1 Answers1

2

One possibility would be to create a base class for each level and an empty handler class deriving from it:

Base classes:

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

Handler classes:

public class ZooKeeper<T> : ZooKeeperBase<ZooKeeper<T>, T>
    where T : Animal
{
}

public class ExperiencedZooKeeper<T>
    : ExperiencedZooKeeperBase<ExperiencedZooKeeper<T>, T>
    where T : Animal
{
}

Usage would be just as you showed in your question.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • This is known as the "curiously recurring template" pattern, and I agree that sometimes it's the way to go, especially when consumers of your classes are mostly going to dealing with the concrete types and the base types are going to be pretty well hidden. I have to wonder if it's worth having an abstract `ExperiencedZooKeeperBase<,>` class, though. Couldn't `ExperiencedZooKeeper<,>` derive directly from `ZooKeeperBase<,>`? – Jeremy Todd May 08 '13 at 14:20
  • @JeremyTodd: In this concrete instance, this would be possible. However, as soon as there is another layer of inheritance that base class is needed. – Daniel Hilgarth May 08 '13 at 14:22
  • Ah, I see. Since your setup is designed to only require one type parameter for the concrete classes, you're right. – Jeremy Todd May 08 '13 at 14:32
  • Thanks Daniel, do you have ideas about the Favorite call I described at the end of the question? – Jaap May 08 '13 at 18:12