Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

generics covariance and explicit casting

If I try and do:

IDictionary<uint, IEnumerable<string>> dict = new Dictionary<uint, List<string>>();

I get the error:

error CS0266: Cannot implicitly convert type 'System.Collections.Generic.Dictionary>' to 'System.Collections.Generic.IDictionary>'. An explicit conversion exists (are you missing a cast?)

If I add the cast:

IDictionary<uint, IEnumerable<string>> dict = (IDictionary<uint, IEnumerable<string>>)new Dictionary<uint, List<string>>();

Then it compiles.

Why do I need the explicit cast? And is it safe? I thought the whole point on covariance was the ability to implicitly cast safely?

EDIT: C# prevents unrelated casting eg

string s = (string)0L;

error CS0030: Cannot convert type 'long' to 'string'

It does allow explicit downcasting of related types when you know that the object is actually a subclass:

Animal animal = new Cat();
Cat cat = (Cat)animal;

I am confused why the compiler is offering, and allowing me to explicitly cast to an IDictionary with incompatible types.

like image 238
GazTheDestroyer Avatar asked Dec 06 '11 15:12

GazTheDestroyer


People also ask

What is generic covariance?

Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.

What is difference between covariance and Contravariance?

In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.

What is the difference between covariance and Contravariance in delegates?

Covariance permits a method to have return type that is more derived than that defined in the delegate. Contravariance permits a method that has parameter types that are less derived than those in the delegate type.

What variance is imposed on generic type parameters?

Unlike simple arrays, the different parameterizations of generic types in Java are invariant by default. For example, a List<Pet> is a totally different thing than List<Dog>, they can not be substituted. The variance of the type parameter can be specified when using a generic type. (This is called use-site variance.


2 Answers

IDictionary<TKey, TValue> is not covariant, either for TKey or TValue.

Covariance would mean that IDictionary could solely produce TKey/TValue types, but since it can produce as well as consume them, it cannot be covariant, nor contravariant for that matter.

I'll define covariance / contravariance in common terms;

IProducer<out T> is covariant, so this means that it only produces T types. Thus when you pass it to a reference to an IProducer with a more abstract T, the cast is implicit, because the following statement is true: "A producer of apples IS a producer of fruit". (to be opposed to "A producer of fruit is not necessarily a producer of apples")

IConsumer<in T> is contravariant, which means in only consumes T types. When you pass it to a reference to a more concrete T, the cast is implicit, because the following statement is true: "A consumer of fruit IS a consumer of apples". (to be opposed to "A consumer of apples is not necessarily a consumer of any fruit")

What this means in the case of IDictionary, regarding specifically the TValue here:

IDictionary has methods that produce TValues as well as methods that consume TValues. That being said, it means it wasn't (and couldn't) declared as either covariant or contravariant. (see http://msdn.microsoft.com/en-us/library/s4ys34ea.aspx - there is no "out" or "in" in the generic interface definition)

This means that when you try to implicitely cast your Dictionary<uint, List<string>> into an IDictionary<uint, IEnumerable<string>>, the compiler says "Wait a minute, the object you have built can only accept List<string> in an Add method but you're putting it into a reference that will allow any IEnumerable<string> in, which is a larger subset. If you Add anything that is an IEnumerable<string> but isnt a List<string>, it won't work." It doesn't (and can't) allow it implicitely, which is why you need the hard cast.

(thanks to mquander for the specific example)

like image 65
CWilliams Avatar answered Sep 30 '22 14:09

CWilliams


It is not safe. For example, you could now write dict.Add(5, new string[0]), which would blow up, since a string[] is not a List<string>. The fact that it is unsafe is why you need the cast.

Edit to address your updated concern:

C# allows any explicit cast from any reference type S to any interface T ("provided S is not sealed and provided S does not implement T.") This behavior is specified in section 6.2.4 of the language spec. So this is legal:

var foo = (IList<ICollection<IEnumerable<IntPtr>>>)new Uri(@"http://zombo.com");

I can't say why this is the case, other than the fact that the C# type system was originally even more constrained than it is today (e.g. no generics, no variance) so I'm sure that there were a lot of cases in which being able to hack around it with casts was very convenient.

like image 23
mqp Avatar answered Sep 30 '22 12:09

mqp