GazTheDestroyer
GazTheDestroyer

Reputation: 21261

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.

Upvotes: 9

Views: 2901

Answers (3)

CWilliams
CWilliams

Reputation: 213

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)

Upvotes: 9

mqp
mqp

Reputation: 72015

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.

Upvotes: 7

Azhar Khorasany
Azhar Khorasany

Reputation: 2709

You can use

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

You are changing the TValue type in your code to a concrete List implementation. That will not work. you have to use the same definition as the declaring type.

With the above, you can use it as:

    dict.Add(1, new List<string>());

etc.

Upvotes: 5

Related Questions