Martijn Kooij
Martijn Kooij

Reputation: 1430

C# Generic class as parameter with same T as called method of generic class

I would like call:

Question<Entity> question = 
    Question<Entity>.Create(
        Choice.Create().
            AddFollowUpQuestion(Question.Create()).
            AddFollowUpQuestion(Question.Create()), 
        Choice.Create()
    );

But the best C# allows me to do is:

Question<Entity> question = 
    Question<Entity>.Create(
        Choice<Entity>.Create().
            AddFollowUpQuestion(Question<Entity>.Create()).
            AddFollowUpQuestion(Question<Entity>.Create()), 
        Choice<Entity>.Create()
    );

I am trying to clean some code up, basically just adding syntactic sugar so some definitions I have to make a lot of are easier to read.

Both intelisense and the compiler know they are expecting the parameter to be of type Choice because it is a method of the generic class. But it still requires me to type out the type of T for the passed argument.

A bit more abstract: I am trying to create a top level generic class in which all properties which are also generic types will be using the same type for T.

Can anyone help me solve this puzzle? Or at least explain me why I have to type the same Type over and over again?

Simplified class definitions:

public class Question<T> where T : Entity
{
    public static Question<T> Create(params Choice<T>[] choices)
    {
        return new Question<T>
        {
            Choices = choices
        };
    }

    private Choice<T>[] Choices { get; set; }
}

public class Choice<T> where T : Entity
{
    public static Choice<T> Create()
    {
        return new Choice<T>();
    }

    public Choice<T> AddFollowUpQuestion(Question<T> followUpQuestion)
    {
        FollowUpQuestions.Add(followUpQuestion);

        return this;
    }

    private static List<Question<T>> FollowUpQuestions { get; set; }
}

public abstract class Entity
{

}

Upvotes: 2

Views: 132

Answers (3)

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726519

C# can infer the method that you want to call based on the type of a parameter, but it cannot infer the type of the class on which to call a method that produces a parameter of the required type: the "type inference magic" goes only one way.

Essentially, you giving the compiler Choice.Create() expression and the fact that its result is being passed to a method expecting Choice<Entity>, and asking it to deduce that Choice is actually a generic type (despite the fact that there may be a non-generic Choice in the system), and it has a Create() method that returns Choice<T>. Although the compiler could possibly do it, implementation would be expensive, and could potentially be a breaking change.

However, you can make a generic helper method that would provide the same T to multiple classes, like this:

static Question<T> MakeQuestion<T>() {
    return Question<T>.Create(Choice<T>.Create());
}

Now you can call

Question<Entity> question = MakeQuestion<Entity>();

and pass the type parameter only once.

Edit: As far as the more elaborate example from your edit is concerned, you should be able to shorten the API by introducing a factory that is generic on Entity, and lets you create questions, follow-ups, etc.

class QuestionFactory<T> {
    public Question<T> CreateQuestion() {
        ...
    }
    public Choice<T> CreateChoice() {
        ...
    }
}

Now you can do this:

var qf = new QuestionFactory<Entity>();
var question = qf.CreateQuestion(
    qf.CreateChoice().
        AddFollowUpQuestion(qf.CreateQuestion()).
        AddFollowUpQuestion(qf.CreateQuestion()), 
    qf.CreateChoice()
);

Upvotes: 1

Ali Nazem
Ali Nazem

Reputation: 116

One way is to change Question.Create() to not expect a Choice to be provided, rather it creates the Choice itself. It makes the code a little simpler and you can achieve your goal.

public class Question<T> where T : Entity
{
    public static Question<T> Create()
    {
        return new Question<T>
        {
            Choice = Choice<T>.Create()
        };
    }

    private Choice<T> Choice { get; set; }
}

public class Choice<T> where T : Entity
{
    public static Choice<T> Create()
    {
        return new Choice<T>();
    }
}

public abstract class Entity
{

}

Depending on the context, this can be a positive change as the responsibility of creation of a Choice is moved to the Question, in other words you abstract the callers of Question.Create() from the hassle of creation of Choice.

On the other hand, it increases the coupling of Question and Choice. Which one is prefered, depends on the rest of the architecture.

Of course, I assumed that T is really needed in Choice.

Upvotes: 0

erikkallen
erikkallen

Reputation: 34391

A common thing to do is to create non-generic classes for the factory method:

public static class Question {
    public static Question<T> Create<T>(Choice<T> choice) {
        return Question<T>.Create(choice);
    }
}

...
Question<Entity> question = Question.Create(Choice<Entity>.Create());

Upvotes: 0

Related Questions