Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement generic method with constraints

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.

like image 310
5earch Avatar asked Aug 09 '13 17:08

5earch


1 Answers

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.

like image 154
Servy Avatar answered Oct 29 '22 22:10

Servy