0

Let's face it, I am still having some difficulties to understand the constrains when it's time to use covariance and contravariance in generics.

I wonder, why if I have this:

public interface IFasterListCov<out T>
{}

public interface IFasterListCon<in T>
{}

public class FasterList<T> : IList<T>, IFasterListCov<T>, IFasterListCon<T>

The third cast fails:

public void QueryNodes<T>() where T:INode
{
     //somehow I can convert IFasterListCon<INode> to IFasterListCon<T>
     IFasterListCon<INode> nodes = (IFasterListCon<INode>)_nodesDB[type];
     //I guess this works because _nodesDB[type] is indeed a FasterList<T> object
     //note: I am wrong, I can cast whatever INode implementation, not just T, which made me very confused :P
     IFasterListCon<T> nodesT = (IFasterListCon<T>)nodes; 
     //I can't cast IFasterListCon<T> back to FasterList<T>
     FasterList<T> nodeI = nodesT as FasterList<T>; //null
}

Dictionary<Type, IFasterListCov<INode>>  _nodesDB;

to be clear _nodesDB[type] is a FasterList<T> declared through IFasterListCov<INode>
sebas
  • 1,045
  • 12
  • 27
  • What is the _actual_ type of `nodesT`? You're doing a _downcast_ which only works if the underlying type is compatible. – D Stanley May 19 '16 at 20:59
  • you mean what is T? T must implement INode (the constrain is in the where clause)...ah ok _nodesDB contains all FasterList – sebas May 19 '16 at 21:00
  • 1
    A `FasterList` is not a `FasterList` unless `T` is `INode`. – Lee May 19 '16 at 21:02
  • so the fact T implements INode is not enough? I mean in memory is still a reference in both cases right? The datastructure should be compatible. – sebas May 19 '16 at 21:03

2 Answers2

1

In the scenario where you're calling QueryNodes<MyNode>, in order for your last cast to get a non-null value, the actual instance that you get with _nodesDB[type] must be a FasterList<MyNode>. It's not good enough for it to be FasterList<SomeOtherMostlyCompatibleNode>.

The runtime is very strict about types, it keeps track of the actual runtime types of everything involved, it's not good enough for the data types to be similar, or for you to only have MyNode objects populating your FasterList<SomeOtherMostlyCompatibleNode>, or anything else. If the types are not exactly what they should be, you need to do some sort of programmatic conversion, not just cast.

Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • wait sorry, I was not clear...the FasterList node contains MyNode elements, it's just that I declare it through INode. So FasterList.Add(MyNode)...pseudo code if makes sense. – sebas May 19 '16 at 21:11
1

MyType : IMyType does not make Generic<IMyType> and Generic<MyType> related in any way.

In your particular case it is likely that nodesT is FasterList<Node> which is not FasterList<INode>.

Note that this conversion work for interface which support variance (co/contra) when you can specify in/out as you see in successful conversion to interface. See one of many questions for details - i.e. Generic Class Covariance.

There is also excellent answer about List co-variance - C# variance problem: Assigning List<Derived> as List<Base> which shows that List<Derived> and List<Base> can't be cast between each other:

List<Giraffes> giraffes = new List<Giraffes>();
List<Animals> animals = new List<Animals>() {new Lion()};

(giraffes as List<Animals>).Add(new Lion()); // What? Lion added to Girafes
Giraffe g = (animals as List<Giraffes>)[0] ; // What? Lion here?
Community
  • 1
  • 1
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • FasterList is not FasterList even if Node implements INode? – sebas May 19 '16 at 21:15
  • Yes - see awesome explanation with Giraffes - http://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase (talks about concrete classes, but it applies the same to interface/class pair) – Alexei Levenkov May 19 '16 at 21:16
  • ok I understand that the language wants to protect a FasterList to be casted to IFasterListCon...but in this case it's the other way around. I am casting to the specialization, not the generalization. – sebas May 19 '16 at 21:25
  • @sebas I've updated my answer with sample based on Jon's answer that possibly makes it clear. – Alexei Levenkov May 19 '16 at 21:34
  • the example is clear, it's just that in that context there is an upcasting (giraffe->animals), while I am downcasting (INode->T) however I am noticing that I can assign to IFasterListCon whatever class implements INode, which made me even more confused, so I think there is something I am still missing. Probably my confusion comes to the fact that I believe that T and INode are related, while the language doesn't assume that. Thanks a lot for the help! – sebas May 19 '16 at 21:40
  • 1
    Ok I think I understood, IFasterListCov could potentially hold different type of INode...my confusion is due to the fact that I was looking at it from the wrong angle. I was seeing IFasterListCov as a generalization of FasterList not INode as a generalization of T. BTW this is a good answer too: http://stackoverflow.com/questions/2719954/understanding-covariant-and-contravariant-interfaces-in-c-sharp?rq=1 – sebas May 19 '16 at 21:52