I am having the following issue relating to Generics CoVariance / ContraVariance Conflict.
public interface IEmailTemplateInput
{
List<string> ToEmail { get; set; }
string Subject { get; set; }
string FromEmail { get; set; }
}
public interface IEmailCommunicationTemplate<in TInputData> where TInputData : IEmailTemplateInput
{
void ProcessTemplate(TInputData input);
Task ProcessTemplateAsync(TInputData input);
}
// Below is the implementation using above interfaces
public class RiskPeerReviewNotificationTemplate
: IEmailCommunicationTemplate<RiskPeerReviewNotificationTemplate.Input>
{
private readonly IEmailService _emailService;
private readonly ITemplateService _templateService;
public class Input : IEmailTemplateInput
{
public string TenantName { get; set; }
public string FirstName { get; set; }
public string CallToActionUrl { get; set; }
}
public RiskPeerReviewNotificationTemplate(IEmailService emailService, ITemplateService templateService)
{
_emailService = emailService;
_templateService = templateService;
}
protected override Task ProcessAsync(Input input)
{
//... Method implementation
}
}
Now I would like to register IEmailCommunicationTemplate and based on input parameter (EmailTemplateType in this case), want to generate different implementations.
But I run into problems during Autofac registration
// Register email templates
iocBuilder.Register<IEmailCommunicationTemplate<IEmailTemplateInput>>((c, p) =>
{
var type = p.TypedAs<EmailTemplateType>();
switch (type)
{
case EmailTemplateType.NewUserNotificationTemplate:
return new NewUserNotificationTemplate(c.Resolve<IEmailService>(),
c.Resolve<ITemplateService>());
case EmailTemplateType.RiskPeerReviewNotificationTemplate:
return new RiskPeerReviewNotificationTemplate(c.Resolve<IEmailService>(),
c.Resolve<ITemplateService>());
default:
throw new ArgumentException("Invalid Email Template type");
}
})
.As<IEmailCommunicationTemplate<IEmailTemplateInput>>();
Now obviously I can't do this because of contraVariance. Actual issue in above registration is because I am effectively doing something like
IEmailCommunicationTemplate test2 = new RiskPeerReviewNotificationTemplate(emailService, templateService);
which isn't possible...
I can't change IEmailCommunicationTemplate where TInputData : IEmailTemplateInput to IEmailCommunicationTemplate where TInputData : IEmailTemplateInput
because TInputData also comes as parameter in methods (contraVariance nature)...
One solution which was proposed on stackoverflow
Generics, Covariance/Contravariance, etc
that would resolve compilation issue :
1) Have two interfaces
interface IEmailContravarianceCommunicationTemplate<out TInputData> where TInputData : IEmailTemplateInput
and
interface IEmailCovarianceCommunicationTemplate<out TInputData> where TInputData : IEmailTemplateInput
2) Have RiskPeerReviewNotificationTemplate implement both.
3) Use interface depending on their context