Z .
Z .

Reputation: 12837

chaining methods in base and derived class

I have 2 classes, one derived from the other:

class Animal
{
    public Animal AnimalMethod() 
    {
        // do something 
        return this; 
    }   
}

class Dog : Animal
{
    public Dog DogMethod()
    {
         // do something
         return this;
    }
}

var dog = new Dog();
dog.DogMethod().AnimalMethod(); // 1 - this works   
dog.AnimalMethod().DogMethod(); // 2 - this doesn't

How can I change my declaration(s) to be able to call the methods in the order "2" above in order to achieve a more fluent api?

Upvotes: 6

Views: 1771

Answers (3)

John Wu
John Wu

Reputation: 52280

Use generic extension methods

Fluent/chaining methods work best as generic extension methods. A generic extension method knows the type of the instance variable and can return it as the same type that was passed in.

class Animal
{
    public string CommonProperty { get; set; }
}

class Dog : Animal
{
    public string DogOnlyProperty { get; set; }
}

static class ExtensionMethods
{
    static public T AnimalMethod<T>(this T o) where T : Animal
    {
        o.CommonProperty = "foo";
        return o;
    }
    static public T DogMethod<T>(this T o) where T : Dog
    {
        o.DogOnlyProperty = "bar";
        return o;
    }

}

class Example
{
    static public void Test()
    {
        var dog = new Dog();
        dog.DogMethod().AnimalMethod(); // 1 - this works   
        dog.AnimalMethod().DogMethod(); // 2 - this works now

        Console.WriteLine("CommonProperty = {0}", dog.CommonProperty);
        Console.WriteLine("DogOnlyProperty = {0}", dog.DogOnlyProperty);

        var animal = new Animal();
        animal.AnimalMethod();
        //animal.DogMethod();                //Does not compile
        //animal.AnimalMethod().DogMethod(); //Does not compile
    }
}

Output:

CommonProperty = foo

DogOnlyProperty = bar

A workaround if you need private/protected access

One disadvantage of extension methods is that they cannot access private or protected members. Your instance method could. This hasn't been a problem for me (and it seems it's not an issue for the entire LINQ library either, which are written as extension methods). But there is a workaround if you need access.

You will need to implement the "chaining" method twice-- once as an interface method on the instance and a simple wrapper (one line of code) as an extension method that simply calls the first method. We use an interface method on the instance so that the compiler won't try to pick the instance method over the extension method.

interface IPrivateAnimal
{
    Animal AnimalMethod();
}

interface IPrivateDog
{
    Dog DogMethod();
}

class Animal : IPrivateAnimal
{
    protected virtual string CommonProperty { get; set; }  //notice this is nonpublic now

    Animal IPrivateAnimal.AnimalMethod()  //Won't show up in intellisense, as intended
    {
        this.CommonProperty = "plugh";
        return this;
    }
}

class Dog : Animal, IPrivateDog
{
    private string DogOnlyProperty { get; set; }  //notice this is nonpublic now

    Dog IPrivateDog.DogMethod()  //Won't show up in intellisense
    {
        this.DogOnlyProperty = "xyzzy";
        return this;
    }
}

static class ExtensionMethods
{
    static public T AnimalMethod<T>(this T o) where T : class, IPrivateAnimal
    {
        return o.AnimalMethod() as T;  //Just pass control to our hidden instance method
    }
    static public T DogMethod<T>(this T o) where T : class, IPrivateDog
    {
        return o.DogMethod() as T;  //Just pass control to the instance method
    }
}

class Example
{
    static public void Test()
    {
        var dog = new Dog();
        dog.DogMethod().AnimalMethod(); 
        dog.AnimalMethod().DogMethod(); 


        Console.WriteLine("CommonProperty = {0}", typeof(Dog).GetProperty("CommonProperty", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(dog));
        Console.WriteLine("DogOnlyProperty = {0}", typeof(Dog).GetProperty("DogOnlyProperty", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(dog));
    }
}

Output:

CommonProperty = plugh

DogOnlyProperty = xyzzy

Upvotes: 10

BradleyDotNET
BradleyDotNET

Reputation: 61369

There is one trick you could use to do this; though I'm not sure I would recommend it. Make Animal generic and all your fluent methods return the type parameter:

class Animal<T> where T : Animal<T>
{
    public T AnimalMethod() { return (T)this; }
}

Now your Dog inherits from the Dog form of animal

class Dog : Animal<Dog>
{
    public Dog DogMethod() { return this; }
}

Now since the initial method will return a Dog, you can call DogMethod on it. This will be very hard to read; but it would accomplish your goal.

I did test this in C# interactive and it appears to work.

Apparently this is called the "Curiously Recurring" Pattern among other things. https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Upvotes: 7

Marc Gravell
Marc Gravell

Reputation: 1063569

The only simple way to do that would be (in Dog):

public new Dog AnimalMethod() 
{
    base.AnimalMethod();
    return this; 
}

This is "method hiding".

Upvotes: 2

Related Questions