简体   繁体   中英

Ninject - Managing Inconvariance of generic types?

I am having trouble using Ninject to load my generic type implementations given the following simplified interface/class structure.

public interface IEntry {}

public class TestEntry : IEntry {}

public interface IDBConnection<T> {}

public class DBConnection<T> : IDBConnection<T> where T : IEntry {}

I am using binds within my loaded NinjectModule:

Bind<IEntry>().To<TestEntry>();
Bind(typeof(IDBConnection<>)).To(typeof(DBConnection<>));

I want to fetch an instance of DBConnection<TestEntry> with a call of:

Kernel.TryGet<IDBConnection<IEntry>>();

However this just returns an open instance type of DBConnection<IEntry> ; I have been able to return an instance of DBConnection<TestEntry> if I change my Kernel.Get call to:

Kernel.TryGet<IDBConnection<TestEntry>>();

I understand that generics are incovariant but it seems that we circumvent the entire purpose of DI/IOC if I need to stipulate the implementation of my generic class in order for Ninject to load it... So I must be either binding, fetching or understanding things incorrectly.

Furthermore I tried a different approach to binding/loading:

Bind<IEntry>().To<TestEntry>();
Bind(typeof(IDBConnection<IEntry>)).To(typeof(DBConnection<TestEntry>));

and

Kernel.TryGet<IDBConnection<IEntry>>();

However, this yields the exception:

System.InvalidCastException : Unable to cast object of type 'DBConnection 1[TestEntry]' to type 'IDBConnection 1[IEntry]'.

This is because the generic type IDBConnection<IEntry> is not covariant with DBConnection<TestEntry> right?

I want to be able to Ninject DBConnection<TestEntry> into my IDBConnection<IEntry> declaration for consumption; however incovariance of generics seems to disallow this. What's the solution?

Edit: Here is a unit Test to demonstrate/explain

    public interface IEntry { }

    public class TestEntry : IEntry { }

    public interface IDBConnection<T> where T : IEntry { }

    public class DBConnection<T> : IDBConnection<T> where T : IEntry { }

    class TestModule : NinjectModule
    {
        public override void Load()
        {
            Bind<IEntry>().To<TestEntry>();
            Bind(typeof(IDBConnection<IEntry>)).To(typeof(DBConnection<TestEntry>));
        }
    }

    [Test]
    public void NinjectGenericLoadTest()
    {
        /// this loads the expected type from interfaces however is useless
        /// since loaded against a "var" 
        ///(runtime casts knowing the impl would be required to use)
        StandardKernel kernel = new StandardKernel(new TestModule());
        var ninjected = kernel.TryGet(typeof(IDBConnection<IEntry>));
        Assert.IsInstanceOf<DBConnection<TestEntry>>(ninjected);

        /// The following is what I want but it won't compile 
        ///:"Cannot implicitly convert type 'object' to 
        ///'EasyMongo.Contract.IReader<EasyMongo.Contract.IEasyMongoEntry>'. 
        /// An explicit conversion exists (are you missing a cast?)"
        //kernel = new StandardKernel(new TestModule());
        //IDBConnection<IEntry> ninjectedInterface = kernel.TryGet(typeof(IDBConnection<IEntry>));
        //Assert.IsInstanceOf<DBConnection<Entry>>(ninjectedInterface);

        /// this throws System.InvalidCastException : Unable to cast object of type 
        /// 'DBConnection`1[EasyMongo.Test.Base.RandomTest+Entry]' 
        /// to type 'IDBConnection`1[EasyMongo.Test.Base.RandomTest+IEntry]'.
        /// this is due to incovariance of generic types such that DBConnection<Entry> 
        /// is not a IDBConnection<IEntry> 
        IDBConnection<IEntry> ninjectedInterface = (IDBConnection<IEntry>)kernel.TryGet(typeof(IDBConnection<IEntry>));
        Assert.IsInstanceOf<DBConnection<TestEntry>>(ninjectedInterface);
    }

Ninject will always return the type you ask for. If you ask for IDBConnection<IEntry> then you will get that type if you ask for IDBConnection<TestEntry> . There is no super logic that will analyze your code and get you a different type then the one you are asking for.

But asking for things like IDBConnection directly is the wrong way to use Ninject anyway. You should inject it using constructor injection:

 public class NeedDbConnection<T> {
      public NeedDbConnection(IDBConnection<T> connection) { ... } 
 }

That way you get your specific db connection appropriate for that class.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM