Is it possible to return generic class based on enum input?

Viewed 82

I am writing a small email templating engine using Razor syntax to fill in the tokens. I have a few email types:

public enum EmailType { Welcome, Reminder }

These types have corresponding templates and models, so for example Welcome email has a template:

<p>Welcome, @Model.Name</p>

and a corresponding model:

public class WelcomeModel { public string Name { get; set; } }

Now I wanted to create a method that will force a correct model for given enum, I would imagine something like this:

public ITemplate<T> GenerateTemplate(EmailType emailType)
{
    switch (emailType)
    {
        case EmailType.Welcome:
            return new EmailTemplate<WelcomeModel>();

        case EmailType.Reminder:
            return new EmailTemplate<ReminderModel>();
    // ...
}

where EmailTemplate<T> : ITemplate<T>, so then I could chain the method:

engine
    .GenerateTemplate(EmailType.Welcome)
    .WithModel(new WelcomeModel()) // this knows it wants WelcomeModel
                                   // and should complain with compiler error otherwise

The code that I've shown here does not compile because T is unknown. However this T cannot be infered:

public ITemplate<T> GenerateTemplate<T>(EmailType emailType)

which leaves me with:

engine
    .GenerateTemplate<WelcomeModel>(EmailType.Welcome)
    .WithModel(new WelcomeModel());

That works, but I feel like I'm passing redundant information - both enum and model, while you can deduce one from another. I am not sure if I'm missing something from C# or maybe my whole concept is not good. I think I'm in a dead end since I think I cannot return two separate, strongly typed classes from one method.

Is it possible to return generic model based on enum input?

3 Answers

Is it possible to return generic class based on enum input?

No. Not in a way that would be useful to the caller.

Your method returns ITemplate<T>. But T has to be defined as something at compile time. You cannot defer the definition to runtime, except by using late-binding (i.e. dynamic) or non-type-safe mechanisms (which negates the whole point of using generics).

Possibly if you could reframe your question and explain why it is you think it's reasonable to call a method that returns an open generic type ITemplate<T> without the call site understanding what the type parameter T actually is, a helpful solution could be found.

But as stated in your question thus far, the only real answer is, no that can't work and wouldn't make any sense if it could.

Couldn't you just do something like this?

switch (emailType)
{
    case EmailType.Welcome:
        return engine.GenerateTemplate().WithModel(new WelcomeModel());

    case EmailType.Reminder:
        return engine.GenerateTemplate().WithModel(new ReminderModel());
}

public ITemplate<T> GenerateTemplate<T>()
{
    return new EmailTemplate<T>();

    // ...
}

public interface ITemplate<T>
{
    void WithModel(T model);
}

public class EmailTemplate<T> : ITemplate<T>
{
    void WithModel(T model)
    {
        // ...
    }
}

No, because C# does not support true generic polymorphism and diamond operator yet to allow to write:

public Template<> GenerateTemplate(EmailType emailType)
{
  switch (emailType)
  {
    case EmailType.Welcome:
        return new EmailTemplate<WelcomeModel>();
    case EmailType.Reminder:
        return new EmailTemplate<ReminderModel>();
  }
}

The only thing you can do is to simulate that by using a non generic top interface like:

ITemplate<T> : ITemplate

Hence the creator method will returns ITemplate:

public ITemplate GenerateTemplate(EmailType emailType)
{
  switch (emailType)
  {
    case EmailType.Welcome:
        return new EmailTemplate<WelcomeModel>();
    case EmailType.Reminder:
        return new EmailTemplate<ReminderModel>();
  }
}

But in your case if you prefer having true generic you need to create several methods:

EmailTemplate<WelcomeModel> GenerateWelcomeTemplate()
  => new EmailTemplate<WelcomeModel>();

EmailTemplate<ReminderModel> GenerateReminderTemplate
  => new EmailTemplate<ReminderModel>();

Then you will check the enum before calling them.

Doing this is more consistant with your code:

if ( EmailType.Welcome )
  engine.GenerateWelcomeTemplate().WithModel(new WelcomeModel());

Here the code is more clean.

But I don't understand why you provide WithModel(new WelcomeModel()) after creating EmailTemplate<WelcomeModel>... it's redundant, isn't it?

Related