5

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.

4 Answers 4

5

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.

Sign up to request clarification or add additional context in comments.

2 Comments

+1 This is great, if you need to be able to allow BusinessModelBuilder to also be generic.
Genius, that's exactly what I needed! Thanks so much, been trying to figure this one out for a while!
2

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)
   {
       //...

3 Comments

Note that in this case the return type of Build now must be BusinessModel, rather than some type that extends BusinessModel.
@Servy Yes, true - given that the class was BusinessModelBuilder, I am guessing that was the goal. You could still return a type that subclasses BusinessModel, but the result would be a BusinessModel variable.
See my solution, which is a slight modification of yours, but handles the case I describe.
0

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>

Comments

0

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'

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.