When using nested generics, the compiler fails when used directly yet compiles correctly when using constraints.
Example:
public static void Test1<V, E>(this Dictionary<V, E> dict)
where V : IVertex
where E : IEdge<V>
{}
public static void Test2(this Dictionary<IVertex, IEdge<IVertex>> dict){}
The two extension methods above have ostensibly the same signature, but if I was to now try to run code such as:
var dict = new Dictionary<VertexInstance, EdgeInstance>();
dict.Test1();
dict.Test2();
the compiler would err on ‘Test2’ stating that it could not convert to the generic form with the inline nested generic. Personally I find the syntax for Test2 to be more intuitive.
I posted this originally as an answer in a question which asked about differences between using generic constraints and using interfaces directly, but I am curious as to why this happens?
Expanding my comment:
These extension methods of course do not have the same signature.
Dictionary<IVertex, IEdge<IVertex>>is not the same asDictionary<VertexInstance, EdgeInstance>. @Payo is correct; this is a question of variance. Classes cannot be co- or contravariant. Interfaces can, but only if they’re marked for it, and IDictionary cannot be marked for it because it wouldn’t be safe, so changing to IDictionary wouldn’t help here.Consider if this were allowed. Your Test2 implementation could look like this:
EvilVertex and EvilEdge could implement the correct interfaces, but not inherit from VertexInstance and EdgeInstance. The call would then fail at runtime. The call to Test2 is therefore not provably safe, so the compiler does not allow it.
No! The constraints version could not have the same code inside, because you can’t convert from
EvilVertextoV, nor fromEvilEdgetoE. You could force a cast from the type to the type parameter, by casting first toobject, but that would of course fail at run time.Because one purpose of generics is to prove the code’s type safety at compile time.
As mentioned, the call to dict Add is a compiler error for the generic version. It can’t be a compiler error for the interface version, because in the context of Test2, all you know is that you’re converting EvilVertex to IVertex, which is perfectly legal.