如何实现带有约束的通用方法

5

从我的标题可能有点难理解我想要实现什么,所以我会更详细地介绍一下。

我有以下界面:

public interface IModelBuilder<T>
    where T : IStandardTemplateTemplate
{
    M Build<M>(T pTemplate, params object[] pParams) where M : BaseModel;
}

现在我想在我的实际构建器中实现该接口。我使用的构建器用于映射不同的对象类型。因此,这看起来如下:

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;
    }
}

现在的问题是:我无法让约束条件起作用。由于我在接口上定义了约束条件,因此即使我的BusinessModel从BaseModel继承,它也不允许我在实际方法中使用不同的约束条件。它一直告诉我,我的约束条件M必须与接口中的约束条件匹配。我尝试了几种不同的方法,但似乎都没有起作用。
有人知道这是否可以实现吗?我只想在接口中说明允许继承模型。
4个回答

5
以下是一个简短但完整的例子,涉及到您的问题:

这里是一个涉及您问题的简短但完整的例子:

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
    {

    }
}

这段代码无法编译,原因和你的代码一样。接口方法的实现必须对泛型参数有完全相同的限制,而不能有更严格的限制。
你的Build方法只能将类型限制为BaseModel,而不是BussinessModel
如果你修改你的IModelBuilder接口,添加一个额外的类级别的泛型参数,并使用作为约束条件,那么你就可以得到所需的功能。
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;
    }
}

这个解决方案在一定程度上基于Reed的答案,但更进一步。


+1 这很棒,如果你需要让 BusinessModelBuilder 也能够是泛型的。 - Reed Copsey
太棒了,这正是我需要的!非常感谢,我已经试图解决这个问题一段时间了! - 5earch

2
你需要将两者都作为类约束条件添加进去:
public interface IModelBuilder<T, M>
    where T : IStandardTemplateTemplate
    where M : BaseModel
{
    M Build<M>(T pTemplate, params object[] pParams);
}

您可以使用以下内容:

public class BusinessModelBuilder : IModelBuilder<IBusinessTemplate, BusinessModel>
{
   public virtual BusinessModel Build(IBusinessTemplate pTemplate, params object[] pParams)
   {
       //...

请注意,在这种情况下,Build的返回类型现在必须是BusinessModel,而不是扩展BusinessModel的某种类型。 - Servy
@Servy 是的,没错 - 鉴于类名是 BusinessModelBuilder,我猜这就是目标。你仍然可以返回一个子类为 BusinessModel 的类型,但结果将是一个 BusinessModel 变量。 - Reed Copsey
看看我的解决方案,它是你的轻微修改,但处理了我描述的情况。 - Servy

0

你可以稍微不同地定义你的接口,例如包括:

public interface IModelBuilder<T,M>
    where T : IStandardTemplateTemplate
    where M: BaseModel
{
    M Build<M>(T pTemplate, params object[] pParams);
}

并像下面这样使用它

public class BusinessModelBuilder : IModelBuilder<IBusinessTemplate,BusinessModelBuilder>

0
这是因为约束实际上是不同的,所以它不满足接口。
IModelBuilder<IBusinessTemplate> sample = new BusinessModelBuilder();
sample.Build(??) 

编译器期望Build的参数是一个BaseModel,但由于您可以拥有从BaseModel继承但不是BusinessModel的类型,因此这显然是无效的。
现在,为了解决实际问题,您可以使用另一个泛型参数。
 public interface IModelBuilder<TemplateType, ConstraintType>
 {
     public ConstraintType Build<ConstraintType>(TemplateType template, params object[] parameters);
 }

或者你可以改变一下顺序,如果你真的想疯狂起来并获得一些通用类型推断(假设这适用于你的参数):

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);
      }
}

然后,您可以简单地创建多个ModelBinder类并在工厂中进行关联。

示例调用:

  public class BusinessTemplate : IStandardTemplate<BusinessModel> { }

  var businessTemplate = new BusinessTemplate();
  var model = new ModelBinder().Build(businessTemplate); // model is of type 'BusinessModel'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接