【问题标题】:Ninject: Binding an interface with a generic that is also an interfaceNinject:将接口与也是接口的泛型绑定
【发布时间】:2012-09-12 12:21:22
【问题描述】:

我已经搜索过这个问题,但没有运气。我们开始吧。

假设我有一个接口:

interface IQueryRepository<T> where T : class

我想绑定任何请求:

IQueryRepository<IClient>

到:

ConcreteQueryRepository<Client>

我已经尝试了显而易见的方法:

Bind<IGenericQueryRepository<IClient>>().To<ConcreteQueryRepository<Client>>()

但我得到一个错误:

ConcreteQueryRepository&lt;Client&gt; 不能用作类型参数“TImplementation” 泛型类型或方法“Ninject.Syntax.IBindingToSyntax&lt;T&gt;.To&lt;TImplementation&gt;()” 'ConcreteQueryRepository&lt;Client&gt;' 没有隐式引用转换 到'IGenericQueryRepository&lt;IClient&gt;'

但我不明白为什么,因为 GenericQueryRepository 实现了 IGenericQueryRepository 而 Client 实现了 IClient。

我希望 Ninject 给我一个具体的通用存储库,其中 T 是客户端。我希望避免在代码中使用具体类型。

可以吗?

【问题讨论】:

    标签: c#-4.0 generics interface ninject-2


    【解决方案1】:

    这与Covariance and Contravariance有关。

    在您的问题中,您提到了以下内容:

    ... GenericQueryRepository 实现 IGenericQueryRepository,Client 实现 IClient。

    让我们通过使用 fruits 使其更简单:Fruit 实现了 IFruit。我们还将创建一个 Tree 类。

    public interface IFruit { }
    public class Fruit : IFruit { }
    public class Tree<T> where T : IFruit { }
    
    Tree<IFruit> tree = new Tree<Fruit>() // error
    

    这将重现您遇到的相同类型的错误。为什么?很简单。

    虽然 Fruit 实现了 IFruit,但 Fruit Tree 并没有实现 IFruit Tree。 Fruit Tree 和 IFruit Tree 之间没有演员表,尽管你会预料到。它们都是 Tree,但具有 不同 类型参数。它们的类型参数相互关联这一事实并不重要。

    换句话说:Fruit Tree 和 IFruit Tree 之间不可能进行强制转换,因为它们的类型参数不匹配。

    一般来说,当使用泛型进行强制转换时,请确保它们的类型参数匹配。但是,也有一些例外情况。见Variance in Generic Interfaces

    在您的情况下,您可以通过使用 IClient 作为 GenericQueryRepository 类的类型参数来修复它。这样做将允许强制转换,因为类型参数匹配。但我不知道您的应用程序架构,因此此修复可能不适用于您的情况。


    编辑:为了更容易理解,复制粘贴下面的代码,看看编译器是怎么说的。

    interface IFruit { }
    class Fruit : IFruit { }
    interface ITree<T> where T : IFruit { }
    class Tree<T> : ITree<T> where T : IFruit { }
    
    class Program
    {
        static void Main(string[] args)
        {
            ITree<Fruit> test1 = new Tree<Fruit>();   // compiles: type parameters match
            ITree<IFruit> test2 = new Tree<Fruit>();  // fails:    type parameters don't match
            ITree<Fruit> test3 = new Tree<IFruit>();  // fails:    type parameters don't match
            ITree<IFruit> test4 = new Tree<IFruit>(); // compiles: type parameters match
    
            IEnumerable<IFruit> test5 = new List<Fruit>(); // compiles: this is one of the exceptional cases
        }
    }
    

    这应该清楚什么是可能的,什么是不可能的。

    【讨论】:

    • 我确实尝试使用 IClient 作为参数,结果是 Ninject 向 Dapper(一个 db-object 映射器)提供了一个 Dictionary&lt;IClient&gt;,它崩溃了一个异常“......需要一个无参数默认构造函数”。我检查了 Dapper 接收到的类型,它是一个接口,因此没有构造函数。我不知道 Dapper 的内部结构,但我知道它使用反射。因此,这对我来说不是一个解决方案,因为 Dapper 试图在运行时实例化泛型类型 T,这需要 new() 约束。非常感谢您花时间回答我
    • @hlintrup 如果您尝试使用Client 作为两个类型参数怎么办?因此无处使用IClient。这应该可以解决您当前的异常,因为 Client 确实有一个构造函数。
    • 是的,这行得通,这就是我最终要做的。我想这没关系,因为 Client 只是一个 POCO,它是与数据库表行的 1:1 映射。 DI 的真正用途在于行为实体和测试,而不是 POCO。
    【解决方案2】:

    我在尝试将 Dapper 查询绑定到接口类型时遇到了同样的问题,考虑一下,Dapper 无法实例化接口类型似乎是有道理的。

    接口只是一个契约,不知道如何实例化它的具体实现。

    Dapper 需要一个接口类型的具体实现类型,否则 Dapper 还必须知道要实例化接口的哪个具体实现,在这种情况下,Dapper 的行为就像一个 DI 容器,事实上,它不是.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-23
      相关资源
      最近更新 更多