1

I've build a generic (abstract) builder which will provide basic implementation for entity builders that will be used during testing.

This is the entity base class:

public abstract class Entity : IObjectState
{
    [NotMapped]
    public ObjectState ObjectState { get; set; }
}

This is the IKey interface:

public interface IKey
{
    int Id { get; set; }
}

This is the Builder class:

public abstract class Builder<T> where T : Entity, IKey, new()
{
    protected int _id { get; set; }
    protected ObjectState _objectState { get; set; }

    public Builder()
    {
        _objectState = ObjectState.Added;
    }

    public virtual Builder<T> WithId(int id)
    {
        this._id = id;
        return this;
    }

    public virtual Builder<T> HavingObjectState(ObjectState objectState)
    {
        _objectState = objectState;
        return this;
    }

    public static implicit operator T(Builder<T> builder)
    {
        return new T
        {
            Id = builder._id,
            ObjectState = builder._objectState
        };
    }
}

This is a sample UnitBuilder implementation:

public class UnitBuilder : Builder<Unit>
{
    private string _shortDescription;
    private string _longDescription;

    public UnitBuilder WithShort(string shortDescription)
    {
        _shortDescription = shortDescription;
        return this;
    }

    public UnitBuilder WithLong(string longDescription)
    {
        _longDescription = longDescription;
        return this;
    }

    public static implicit operator Unit(UnitBuilder builder)
    {
        return new Unit
        {
            Id = builder._id,
            ObjectState = builder._objectState,
            Short = builder._shortDescription,
            Long = builder._longDescription
        };
    }
}

And this is the problem I'm having:

sample

The error:

Error CS1061 'Builder' does not contain a definition for 'WithShort' and no extension method 'WithShort' accepting a first argument of type 'Builder' could be found (are you missing a using directive or an assembly reference?)



I understand what is going on but I would like a better (more elegant) solution than thirdUnit.

UPDATE:

As per suggestion I added the following to the UnitBuilder class:

public new UnitBuilder WithId(int id)
{
    return (UnitBuilder)base.WithId(id);
}

public new UnitBuilder WithObjectState(ObjectState objectState)
{
    return (UnitBuilder)base.WithObjectState(objectState);
}

But now I don't see any point in the base class... This has to be a general generic base class problem, how do other people handle this? Maybe the thirdUnit solution IS elegant but I'm just being difficult about it? :)

grmbl
  • 2,514
  • 4
  • 29
  • 54
  • 1
    You might declare a new WithId method with the keyword 'new' in the UnitBuilder class which hides the base's method and returns itself as UnitBuilder : public new UnitBuilder WithId(int id); – Abdullah Nehir May 29 '16 at 06:53
  • I agree but doesn't that destroy the use of the base class? – grmbl May 29 '16 at 06:56
  • Its doesn't, you are still using the base class' method, it's just your are calling it explicitly. I can't see how you would do that otherwise. You need your `WithId` method to return whatever type is currently inheriting from it, you can't have that. – GSerg May 29 '16 at 07:00
  • Thanks, I'll try this and post back with the result. – grmbl May 29 '16 at 07:02
  • @GSerg, it works but I don't see any use for the generic base class if I need to new the (only) base class functions... – grmbl May 29 '16 at 07:14
  • @GSerg, I think this is a problem affecting all generic base classes..? – grmbl May 29 '16 at 07:15
  • Seems, you forgot to add `{}` brackets around `.WithId(1), .WithShort("gr")`. – Maciej Los May 29 '16 at 08:10
  • @grmbl Other people [use generics to solve that](http://stackoverflow.com/q/9361401/11683) too. You could introduce a second generic parameter to your `Builder`, e.g. `Builder` and have it return `TBuilder` where appropriate. Or you can ditch your implicit operator and then you only need one generic parameter, ``, because other than in the operator, you are only using `Builder`, not `T` itself. – GSerg May 29 '16 at 11:22
  • @GSerg, thank you! – grmbl May 29 '16 at 11:29

2 Answers2

2

The answer is simple, your base class builder methods have to be called last and cannot be chained with your more specific builder classes as it returns the generic. So simply change your code to:

Unit secondUnit = new UnitBuilder()
    .WithShort("ShortDesc")
    .WithId(10);

That's it!

Ruskin
  • 1,504
  • 13
  • 25
  • 1
    thank you! This works and I'm ashamed I didn't try this myself. But it kinda feels restricted, nonetheless I'm accepting this as answer. – grmbl May 29 '16 at 13:58
  • @grmbl I agree, it is restrictive. The user needs knowledge of what fluent methods are described in the base class and which in the derived class. Breaking some of the SOLID design principles. – Ruskin May 30 '16 at 01:03
0

This is my working final solution:

public abstract class Builder<TEntity, TBuilder>
        where TEntity : Entity, IKey, new()
        where TBuilder : Builder<TEntity, TBuilder>, new()
{
    protected int _id { get; set; }
    protected ObjectState _objectState { get; set; }

    public Builder()
    {
        _objectState = ObjectState.Added;
    }

    public virtual Builder<TEntity, TBuilder> WithId(int id)
    {
        this._id = id;
        return this;
    }

    public virtual Builder<TEntity, TBuilder> WithObjectState(ObjectState objectState)
    {
        this._objectState = objectState;
        return this;
    }

    public static implicit operator TEntity(Builder<TEntity, TBuilder> builder)
    {
        return new TEntity
        {
            Id = builder._id,
            ObjectState = builder._objectState
        };
    }
}
grmbl
  • 2,514
  • 4
  • 29
  • 54