Reputation: 12837
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
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
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
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