Search code examples
c#-4.0genericsinterfaceninject-2

Ninject: Binding an interface with a generic that is also an interface


I have searched this issue but with no luck. Here we go.

Suppose I have an interface:

interface IQueryRepository<T> where T : class

and I want to bind any requests for:

IQueryRepository<IClient>

to:

ConcreteQueryRepository<Client>

I've tried the obvious:

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

But I get an error:

ConcreteQueryRepository<Client> cannot be used as type parameter 'TImplementation' in the generic type or method 'Ninject.Syntax.IBindingToSyntax<T>.To<TImplementation>()' There is no implicit reference conversion from 'ConcreteQueryRepository<Client>' to 'IGenericQueryRepository<IClient>'

But I don't understand why since GenericQueryRepository implements IGenericQueryRepository and Client implements IClient.

I would like Ninject to give me a concrete generic repository where T is Client. I want this to avoid using concrete types in the code.

Can it be done?


Solution

  • This has to do with Covariance and Contravariance.

    In your question you mentioned the following:

    ... GenericQueryRepository implements IGenericQueryRepository and Client implements IClient.

    Let's make it simpler by using fruits: Fruit implements IFruit. We'll also create a Tree class.

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

    This will reproduce the same kind of error you're experiencing. Why? Simple.

    Though Fruit implements IFruit, an Fruit Tree doesn't implement a IFruit Tree. There is no cast possible between the Fruit Tree and the IFruit Tree, although you would expect it. They are both Trees, but with a different type parameter. The fact that their type parameters are related to each other, doesn't matter.

    In other words: there is no cast possible between the Fruit Tree and the IFruit Tree, because their type parameters don't match.

    In general, when casting with generics, make sure their type parameters match. However, there are a few exceptional cases. See Variance in Generic Interfaces.

    In your case, you could fix it by using IClient as type parameter for the GenericQueryRepository class. Doing this will allow casting because the type parameters match. But I don't know your application architecture, so this fix might be inapplicable in your case.


    EDIT: To make it easier to understand, copy paste the code below and see what the compiler says.

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

    That should clear things up about what is and what is not possible.