Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

chaining methods in base and derived class

Tags:

c#

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?

like image 900
Z.D. Avatar asked Aug 04 '17 22:08

Z.D.


People also ask

What is method chaining in Java?

Method chaining is a technique that is used for making multiple method calls on the same object, using the object reference just once. Example − Assume we have a class Foo that has two methods, bar and baz.

What is class method chaining in Python?

Explain Python class method chaining 1 Method Chaining. Method chaining is a technique that is used for making multiple method calls on the same object, using the object reference just once. 2 Example. Simple method chaining can be implemented easily in Python. 3 Output

What happens if both base and derived classes are caught as exceptions?

If both base and derived classes are caught as exceptions, then the catch block of the derived class must appear before the base class. If we put the base class first then the derived class catch block will never be reached. For example, the following C++ code prints “Caught Base Exception“

What is a derived class in C++?

A derived class is a class which takes some properties from its base class. It is true that a pointer of one class can point to other class, but classes must be a base and derived class, then it is possible. To access the variable of the base class, base class pointer will be used.


2 Answers

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

like image 190
John Wu Avatar answered Sep 22 '22 12:09

John Wu


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

like image 28
BradleyDotNET Avatar answered Sep 22 '22 12:09

BradleyDotNET