Returning `this` from a generic parent class requires casting to a child

I am using the builder template , extract the duplicate code in the 'helper' class, but there is one aspect of the duplicate code I'm still unhappy with.

The builder pattern allows you to link your implementation code like this:

Car car = new CarBuilder().Wheels(4).Convertible().Build();

      

Each of the methods CarBuilder

, Wheels

and Convertible

returns the same instance of the builder ( return this

) class , and the method Build

returns the newly created one Car

.

Here's my attempt at creating a generic class:

public class Builder<T> where T : class 
{
    private Func<T, T> func;

    protected void SetInstantiator(Func<T, T> f) => this.func = f;

    protected void Chain(Action<T> action)
    {
        this.ChainFunc(action);
    }

    private ChainFunc(Action<T> action)
    {
        // SNIPPED
    }

    protected T Instantiate() => this.func(null);
}

      

And here is the implementation of my generic constructor:

public class CarBuilder : Builder<Car>
{
    public CarBuilder()
    {
        this.SetInstantiator(c => new Car());
        return this;
    }

    public CarBuilder Wheels(int wheels)
    {
        this.Chain(c => c.SetWheelCount(wheels));
        return this;
    }

    public CarBuilder Convertible()
    {
        this.Chain(c => c.RetractableRoof = true);
        return this;
    }

    public Car Build() => this.Instantiate();
}

      

What worries me is the repetition return this

after every method call Chain

, and I thought I could push it in the method itself Chain

, meaning I want to write code like this:

    public CarBuilder Wheels(int wheels) =>
        this.Chain(c => c.SetWheelCount(wheels));

      

In the builder class, I tried to change the return type from void

to Builder

:

protected Builder Chain(Action<T> action)
{
    this.ChainFunc(action);
    return this;
}

      

... but the compiler says the return type should be Builder<T>

ie

protected Builder<T> Chain(Action<T> action)
{
    this.ChainFunc(action);
    return this;
}

      

OK, fair enough, but in my implementation class I now need to make a cast:

    public CarBuilder Wheels(int wheels) =>
        (CarBuilder)this.Chain(c => c.SetWheelCount(wheels));

      

So, I went over the code again because all methods should now include a cast. Passing a class type from subtype to supertype doesn't seem right.

I think I may be missing something fundamental. Can I avoid repeating the cast and "return this" from each builder implementation method?

+3


source to share


2 answers


One way to keep your logic in a protected area is to add a static method that is called instead of an instance method. A static method can use implicit casting to return the type of the caller

Inside Builder<T>

protected void Chain(Action<T> action)
{
    //local chain logic
}

protected static BT Chain<BT>(BT builder, Action<T> action)
    where BT:Builder<T>
{
    builder.Chain(action);
    return builder;
}

      



Call inside CarBuilder

:

public CarBuilder Wheels(int wheels) => Chain(this , c => c.SetWheelCount(wheels));

public CarBuilder Convertible() => Chain(this, c => c.RetractableRoof = true);

      

+1


source


Don't put Chain

in a base class. Instead, make it a generic extension method:



public static TBuilder Chain<TBuilder, TObject>(this TBuilder @this, Action<TObject> a)
 where TBuilder: Builder<TObject>
 => ...

      

+1


source







All Articles