Search code examples
c#genericscovariancecontravariancestrong-typing

Contra/covariance and nested generics


I have a question on typing and contra/covairance.

Given the following classes

public class BoardItemsHolderRepository<THolder, TBoardItem> : DataRepository<IList<THolder>> 
    where TBoardItem : BoardItem
    where THolder : IBoardItemHolder<TBoardItem>
{
}
public interface IDataRepository<T> : IDataGetRepository<T>, IDataSetRepository<T> where T : class
{
}

public interface IDataGetRepository<out T> where T : class
{
    IObservable<T> GetObservableStream();
    IObservable<T> GetMostRecent();
}

public interface IDataSetRepository<in T> where T : class
{
    void Set(T value);
}

public abstract class DataRepository<T> : IDataRepository<T> where T : class
{
  ..implementation details
}
public class ConstructHolder : IBoardItemHolder<Construct>
{
  ..implementation details
}

Given the above 3 files, can someone explain to me why is the following happening?:

IDataGetRepository<IList<IBoardItemHolder<Construct>>> wontCompile = new BoardItemsHolderRepository<ConstructHolder, Construct>(); //illegal
IDataGetRepository<IList<ConstructHolder>> compile = new BoardItemsHolderRepository<ConstructHolder, Construct>();  //legal

I can't understand why implicit casting for the first line would not work, as the following line compiles(as expected)

IBoardItemHolder<Construct>> compile = new ConstructHolder();

Solution

  • Let's expand the right hand side of the illegal line step by step. First, we start with

    BoardItemsHolderRepository<ConstructHolder, Construct>
    

    This is a DataRepository<IList<THolder>>, so the above is a kind of:

    DataRepository<IList<ConstructHolder>>
    

    which in turn is IDataGetRepository<T>, so the above is a kind of:

    IDataGetRepository<IList<ConstructHolder>>
    

    IDataGetRepository<T> is covariant on T. Recall exactly what this means: If U is a subtype of T, then IDataGetRepository<U> is a subtype of IDataGetRepository<T>. For example, IDataGetRepository<Cat> is a subtype of IDataGetRepository<Animal>, and so an instance of the former type can be assigned to a variable of the latter type.

    However, IDataGetRepository<IList<ConstructHolder>> is not a subtype of IDataGetRepository<IList<IBoardItemHolder<Construct>>> and so cannot be assigned to it. Why? Because IList<ConstructHolder> is not a subtype of IList<IBoardItemHolder<Construct>>! IList<T> is invariant on T!

    So what you are trying to do violates type safety, according to the type-checker. Maybe try using a IEnumerable rather than IList?