Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't add a concrete instance of a generic interface to a generic collection

Tags:

c#

oop

generics

I'll let the code do the talking:

using System.Collections.Generic;

namespace test
{
    public interface IThing { } // can't change this - it's a 3rd party thing

    public interface IThingRepository<T> where T : class, IThing { } // can't change this - it's a 3rd party thing

    public interface IThingServiceInstance<T>
      where T : class, IThing
    {
        IThingRepository<T> Repository { get; set; }
    }

    public class ThingServiceInstance<T> : IThingServiceInstance<T> where T : class, IThing
    {
        public IThingRepository<T> Repository { get; set; }

    }

    public class MyThing : IThing
    {
    }

    class Test
    {
        public void DoStuff()
        {
            IList<IThingServiceInstance<IThing>> thingServiceInstances = new List<IThingServiceInstance<IThing>>();
            // the following line does not compile. Errors are:
            // 1: The best overloaded method match for 'System.Collections.Generic.ICollection<test.IThingServiceInstance<test.IThing>>.Add(test.IThingServiceInstance<test.IThing>)' has some invalid arguments    C:\TFS\FACE\ResearchArea\ArgonServiceBusSpike\Argon_Service_Bus_Spike_v2\Argon.ServiceLayer\test.cs 31  13  Argon.ServiceGateway
            // 2: Argument 1: cannot convert from 'test.ThingServiceInstance<test.MyThing>' to 'test.IThingServiceInstance<test.IThing>'    C:\TFS\FACE\ResearchArea\ArgonServiceBusSpike\Argon_Service_Bus_Spike_v2\Argon.ServiceLayer\test.cs 31  39  Argon.ServiceGateway
            // Why? ThingServiceInstance is an IThingServiceInstance and MyThing is an IThing
            thingServiceInstances.Add(new ThingServiceInstance<MyThing>());
        }
    }
}

If ThingServiceInstance is an IThingServiceInstance and MyThing is an IThing, why can't I add a ThingServiceInstance<MyThing> to a collection of IThingServiceInstance<IThing>?

What can I do to make this code compile?

like image 232
Mark Robinson Avatar asked Oct 02 '22 10:10

Mark Robinson


2 Answers

ThingServiceInstance<MyThing> is NOT a subtype of IThingServiceInstance<IMyThing>, because IThingServiceInstance<T> is invariant in its type parameter <T>.

If you wanna make ThingServiceInstance<MyThing> a subtype of IThingServiceInstance<IMyThing>, then T has to be covariant. In C#, you do that by declaring IThingServiceInstance<T> as such:

public interface IThingServiceInstance<out T>

Edit This, however implies that ThingServiceInstance<T> can only return instances of T, but never have them as method arguments (hence the "out" notation).

Edit2

This is the gist of why your code wasn't compiling. As pointed out, since your ThingServiceInstance<T> exposes an IThingRepository<T> property, that too has to be covariant like so:

public interface IThingRepository<out T> where T : class, IThing { }

As follows, your property has to be get-only (remember, you can only return instances of T, or U<T> for that matter).

like image 185
dcastro Avatar answered Oct 13 '22 10:10

dcastro


You can get it to compile if you declare two of the interfaces coariant, and remove the setter from IThingServiceInstance.

Of course, you can't change the third party interface, so this doesn't really help.

public interface IThingRepository<out T> where T : class, IThing { } // can't change this - it's a 3rd party thing

public interface IThingServiceInstance<out T>
  where T : class, IThing
{
    IThingRepository<T> Repository { get; }
}

If IThingRepository<T> does not declare T covariant, then for

T : A

you don't get

IThingRepository<T> : IThingRespository<A>

and so you cannot have

IThingServiceInstance<T> : IThingServiceInstance<A>

since the type returned by the getter are not "compatible".

like image 31
Rob Avatar answered Oct 13 '22 11:10

Rob