Alexander
Alexander

Reputation: 20224

Polymorphic base class with non-polymorphic child classes and abstraction

Consider I have a TemplateEngine with the three classes:

and the sample implementation:

and now, in the engine, a getter function:

public T GetTemplate<T, U>() where T: TemplateBase<U>, new() where U : TemplateDataBase
{
    var template = new T();
    ...
    return template;
}

Since all implementations of TemplateBase will have exactly one valid value for U, like the sample, the type of U can be infered through the choice of T, and I shouldn't have to provide it to the GetTemplate method.

Other TemplateData classes contain completely different data and one shouldn't be able to use the wrong TemplateData class for a certain template.

If I now remove the U type parameter from the function call, I get "Incorrect number of type parameters", or, if I remove it from the function definition, the getter is no longer valid because "Cannot resolve U".

If I keep the parameter, I still am not allowed to do so because "There is no implicit reference conversion from SampleTemplate to TemplateBase<TemplateDataBase>".

What am I doing wrong here?

Upvotes: 1

Views: 42

Answers (1)

devNull
devNull

Reputation: 4219

Since you are trying to use a type parameter that is a child of the type parameter defined in the GetTemplate method, you'll need to use a covariant type parameter. Which by definition

Enables you to use a more derived type than originally specified

And since variance modifies can only be applied to interfaces or delegates, you'll need to create one of the two. Here's an example of using a generic interface with a covariant type parameter that allows you the type parameter to be implied:

interface ITemplate<out T> where T : TemplateDataBase
{
    Type DataType { get; }
}

class TemplateBase<T> : ITemplate<T> where T : TemplateDataBase
{
    public Type DataType => typeof(T);
}

class TemplateDataBase { }

class TemplateEngine
{
    public T GetTemplate<T>() where T : ITemplate<TemplateDataBase>, new()
    {
        var template = new T();
        return template;
    }
}

class SampleTemplate : TemplateBase<SampleTemplateData> { }

class SampleTemplateData : TemplateDataBase { }

Note the ITemplate<out T>. This is what actually says that the type parameter is covariant.

And here's an example of the use site where the type is being inferred:

static void Main(string[] args)
{
    var engine = new TemplateEngine();
    var sampleTemplate = engine.GetTemplate<SampleTemplate>();

    Console.WriteLine($"{sampleTemplate.DataType.Name}");
    Console.ReadLine();
}

enter image description here

Upvotes: 3

Related Questions