2

I am trying to create an instance of a class using factory builder from the parent.

The use case is basically a library that contains series of classes that inherit from the same class. The main goal was to reduce the amount of code in those classes.

I have an interface that contains two values, IDualValues<T>.

public interface IDualValues<T>
{
    public T FirstValue { get; }
    public T SecondValue { get; }
}

This is an example of what all the classes will look like (changing the method name).

public class Foo : IDualValues<string>
{
    public string FirstValue { get; }
    public string SecondValue { get; }

    public Foo(string firstValue, string secondValue)
    {
        FirstValue = firstValue;
        SecondValue = secondValue;
    }

    public static Foo Create(string firstValue, string secondValue)
    {
        return new Foo(firstValue, secondValue);
    }
}

My point is that I want to use the FactoryBuilder to do Foo.Create instead of a new instance directly in the client application.

The ideal scenario will have a parent class that contains all the logic:

public class DualValuesBuilder<T, CreatedType> : IDualValues<T>
where CreatedType : class, IDualValues<T>
{
    public T FirstValue { get; init; }
    public T SecondValue { get; init; }

    private DualValuesBuilder(T firstValue, T secondValue)
    {
        FirstValue = firstValue;
        SecondValue = secondValue;
    }

    public static CreatedType Create(T firstValue, T secondValue)
    {
       //Creation here
    }
}   

and the other classes will be empty classes just implementing that one:

public class Foo : DualValuesBuilder<string, Foo>
{
}

Notice that I am sending the class itself as a generic parameter to the "builder".

To Achieve my goal, I created a "helper" class to build a class that inherits from IDualValues<T>.

public class DualValuesBuilderHelper<T> : IDualValues<T>
{
    public T FirstValue { get; init;}
    public T SecondValue { get; init; }

    private DualValuesBuilderHelper(T firstValue, T secondValue)
    {
        FirstValue = firstValue;
        SecondValue = secondValue;
    }

    public static IDualValues<T> Create<ReturnType>(T developerK8SClusterValue, T octopusValue)
    where ReturnType : IDualValues<T>
    {
        return new DualValuesBuilderHelper<T>(developerK8SClusterValue, octopusValue);
    }
}

and then I updated the Create method in the class DualValuesBuilder to look like the next:

public static CreatedType Create(T firstValue, T secondValue)
{
    return (CreatedType)DualValuesBuilderHelper<T>.Create<CreatedType>(firstValue, secondValue);
}

But that is giving me an error on the casting: System.InvalidCastException: Unable to cast object of type 'DualValuesBuilderHelper1[System.String]' to type 'Foo'.`

Here there is a fiddle with the code: https://dotnetfiddle.net/IUrgr1

Yes, if I do the following works, but is not what I am trying to achieve.

Foo foo = new Foo()
{
    FirstValue = "f1",
    SecondValue = "f2"
};

note: Using the construtor also works, but I need to make it work with the Create.

Even if I create an explicit (or implicit) operator, the casting still happening.

Am I overthinking, and there is a simple way of accomplishing what I am trying to do? Or is it not possible to do?

Thanks.

TiGreX
  • 1,602
  • 3
  • 26
  • 41

1 Answers1

0

Updated as I'd missed the nuance of the inherited factory method.

Primarily adding new() to the generic constraint will sort you out, and including CreateType must be a child of DualValuesBuilder: where CreateType : DualValuesBuilder<CreateType, DataType>, new()

public interface IDualValues<T>
{
    public T FirstValue { get; init; }
    public T SecondValue { get; init; }
}

public class Foo : DualValuesBuilder<Foo, string>
{
}

public class Foo2 : DualValuesBuilder<Foo2, int>
{
}
    
public class DualValuesBuilder<CreateType, DataType> : IDualValues<DataType>
    where CreateType : DualValuesBuilder<CreateType, DataType>, new()
{
    public DataType FirstValue { get; init; }
    public DataType SecondValue { get; init; }

    public static CreateType Create(DataType firstValue, DataType secondValue)
    {
        return new CreateType
        {
            FirstValue = firstValue,
            SecondValue = secondValue
        };
    }
}

Primarily adding new() to the generic constraint will sort you out: where CreatedType : class, IDualValues<T>, new()

public interface IDualValues<T>
{
    public T FirstValue { get; init; }
    public T SecondValue { get; init; }
}

public class Foo : IDualValues<string>
{
    public string FirstValue { get; init; }
    public string SecondValue { get; init; }
}

public static class DualValuesBuilder
{
    public static CreateType Create<CreateType, DataType>(
        DataType firstValue, 
        DataType secondValue)
        where CreateType : class, IDualValues<DataType>, new()
    {
        return new CreateType
        {
            FirstValue = firstValue,
            SecondValue = secondValue
        };
    }
}
qujck
  • 14,388
  • 4
  • 45
  • 74
  • The problem with this approach is that I cannot do `Foo.Create() ` I have to do `Foo foo = DataValuesBuilder.Create()`. – TiGreX Aug 21 '21 at 22:13
  • @TiGreX why do you need abstract static builder method in a specific class? As for me, it doesn't make much sense. Approach suggested by `@qujck` is better in terms of code decoupling and produces more testable code – AndrewSilver Aug 22 '21 at 06:04
  • @AndrewSilver the problem with that approach is that is not readable if a root type has 10 properties, it will be something like `Bar.Create(defaultvaluesbuilder.create(), defaultvaluesbuilder.create(),...` which is very hard to read (in a PR for example) if you compare it with `Bar.Create(Foo.Create(), Another.Create(),...)`. and in top of that you "won't be using types" as everything is 'IDualVaules`, Which will allow you to swap the parameters without compiling errors. – TiGreX Aug 22 '21 at 07:09