1

I'm having some trouble understanding why a cast I'm performing is throwing runtime exceptions stating that it's an illegal cast. I did a bit of research and found this answer, which lead me to the MSDN article Covariance and Contravariance in Generics. However, I'm still a bit confused so if anyone can help clarify it would be greatly appreciated.

Here's the object hiearchy for 2 class types:

IMongoEntity (interface)
  |   - MongoEntity (abstract)
  |     |    -SalesProject (concrete)
  |     |    -ManagementProject (concrete)

IEntityService<T> where T : IMongoEntity (interface)
  |      -EntityService<T> where T : IMongoEntity (concrete superclass)
  |       |    - MgmtService : EntityService<ManagementProject> (subclass)
  |       |    - SalesService : EntityService<SalesProject> (subclass)

The two non generic services were only created so I could create some specific methods that only apply to those specific types (predefined lookups into the db essentially).

I then have this line, which throws the InvalidCastException:

IEntityService<IMongoEntity> service = fromsales ? 
    (IEntityService<IMongoEntity>)salesService : 
    (IEntityService<IMongoEntity>)mgmtService;

Since both services are derived from the same interfaces & abstract classes and the type parameters used are derived from the same abstract class, then why is this cast illegal?

NOTE: I have workarounds for this, so I'm not really looking for a solution, but instead I want to understand why this is not allowed.

Community
  • 1
  • 1
JNYRanger
  • 6,829
  • 12
  • 53
  • 81
  • if `IEntityService where T : IMongoEntity` was changed to `IEntityService where T : IMongoEntity` I think it would work. You are running in to covariance isues – Scott Chamberlain Sep 25 '15 at 16:19
  • @ScottChamberlain Isn't the out keyword in generics for return types though? The methods all return Task or Task – JNYRanger Sep 25 '15 at 16:25
  • 4
    Imagine `IEntityService` has an `Add(T item)` method. (We don't know whether or not it does, but the compiler doesn't care. It *could*.) Now think what that means for an `IEntityService` - you can add *any* mongo entity to it. Now, can you add a `ManagementProject` to a `SalesService`? Nope... *That's* why it's invalid. – Jon Skeet Sep 25 '15 at 16:25
  • @JonSkeet Ah, it's so simple! That makes sense. – JNYRanger Sep 25 '15 at 16:27
  • And `out` is what tells the compiler that you do not have any `Add(T item)` calls. – Scott Chamberlain Sep 25 '15 at 16:39

1 Answers1

3

MyType<Base> and MyType<Derived> do not have any inheritance relationship, even if Derived derives from Base. The two generic types are just two different types.

One way to come around this problem is to have a non-generic interface as base interface:

public interface IEntityService
{
    void DoSomething(object item);
}

public interface IEntityService<T> : IEntityService
{
    void DoSomething(T item);
}

This pattern is used in the .Net Class Library (for e.g. IEnumerable/IEnumerable<T>, IList/IList<T>).

If you know that your interface uses the generic type (T) only for outputs, you can use the out keyword IMyInterface<out T>. You can then provide a more derived type for T. This is called covariance. The return values of the methods will then yield a more derived type as expected by the consumer and this is ok.

If it uses the generic type only for inputs, use the in keyword IMyInterface<in T>. You can then provide a less derived type for T. This is called contravariance. The input arguments of the methods will then get a more derived type as expected and this is ok.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188