Reputation: 289
from my title it might be a little hard to understand what I'm trying to achieve, so I'll go a little further into detail.
I have the following interface:
public interface IModelBuilder<T>
where T : IStandardTemplateTemplate
{
M Build<M>(T pTemplate, params object[] pParams) where M : BaseModel;
}
Now I want to implement the interface in my actual builder. The builder I use to map different object types. So this looks as follows:
public class BusinessModelBuilder : IModelBuilder<IBusinessTemplate>
{
public virtual M Build<M>(IBusinessTemplate pTemplate, params object[] pParams) where M : BussinessModel
{
var businessModel = Activator.CreateInstance<M>();
// map data
return businessModel;
}
}
Now the problem is the following. I can't get the constraint to work. Since I defined the constraint on the interface it won't let me use a different constraint on my actual method, even though my BusinessModel inherits from BaseModel. It keeps telling me my constraint M must match the constraint from the interface. I tried several different approaches, but none seem to work.
Does anyone know if or how this can be achieved? I just want to tell my constraint in the interface that inherited models are allowed.
Upvotes: 5
Views: 1248
Reputation: 203802
Here's a short but complete example of your problem:
public class Parent { }
public class Child { }
public interface Interface
{
void Foo<T>() where T : Parent;
}
public class Implementation : Interface
{
public void Foo<T>() where T : Child
{
}
}
This won't compile, for the same reason that yours won't. The implementation of the interface method must have the exact same constraint on the generic argument, it can't have a more restrictive constraint.
Your Build
method can only constrain the type to BaseModel
, not BussinessModel
.
If you alter your IModelBuilder
interface to add an additional class level generic argument, and then use that as the constraint, then you can get the desired functionality:
public interface IModelBuilder<T, Model>
where T : IStandardTemplateTemplate
where Model : BaseModel
{
M Build<M>(T pTemplate, params object[] pParams) where M : Model;
}
public class BusinessModelBuilder : IModelBuilder<IBusinessTemplate, BussinessModel>
{
public virtual M Build<M>(IBusinessTemplate pTemplate, params object[] pParams)
where M : BussinessModel
{
var businessModel = Activator.CreateInstance<M>();
// map data
return businessModel;
}
}
This solution is based in part on Reed's answer but which takes it a step further.
Upvotes: 5
Reputation: 41236
This is because the constraint is actually different, so it does not satisfy the interface.
IModelBuilder<IBusinessTemplate> sample = new BusinessModelBuilder();
sample.Build(??)
The compiler would be expecting the arguments to Build
to be a BaseModel
, but since you can have a type inheriting from BaseModel
that is not a BusinessModel
, this is obviously invalid.
Now, to get to the actual meat of the problem, you can solve this with another generic argument.
public interface IModelBuilder<TemplateType, ConstraintType>
{
public ConstraintType Build<ConstraintType>(TemplateType template, params object[] parameters);
}
Alternatively, you can switch things around and get some generic type inference if you really want to go crazy (assuming this is even applicable to your argument):
public interface IStandardTemplate<TModel> { }
public interface IModelBinder<TModel>
{
TModel ApplyParameters(IStandardTemplate<TModel> template, params object[] parameters);
}
public class ModelBuilder
{
public TModel Build<TModel>(IStandardTemplate<TModel> template, params object[] parameters)
{
var model = Activator.CreateInstance<TModel>();
var modelBinder = ModelBinderFactory.CreateBinderFor(model);
return modelBinder.ApplyParameters(template, parameters);
}
}
Then you can simply make various ModelBinder classes and associate them in your factory.
Example call:
public class BusinessTemplate : IStandardTemplate<BusinessModel> { }
var businessTemplate = new BusinessTemplate();
var model = new ModelBinder().Build(businessTemplate); // model is of type 'BusinessModel'
Upvotes: 0
Reputation: 903
You can define your interface little bit differently to include e.g.
public interface IModelBuilder<T,M>
where T : IStandardTemplateTemplate
where M: BaseModel
{
M Build<M>(T pTemplate, params object[] pParams);
}
and use it like below
public class BusinessModelBuilder : IModelBuilder<IBusinessTemplate,BusinessModelBuilder>
Upvotes: 0
Reputation: 564333
You'll need to put both in as class constraints:
public interface IModelBuilder<T, M>
where T : IStandardTemplateTemplate
where M : BaseModel
{
M Build<M>(T pTemplate, params object[] pParams);
}
You can then use:
public class BusinessModelBuilder : IModelBuilder<IBusinessTemplate, BusinessModel>
{
public virtual BusinessModel Build(IBusinessTemplate pTemplate, params object[] pParams)
{
//...
Upvotes: 2