9

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.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103

3 Answers3

9

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)

CWilliams
  • 213
  • 1
  • 7
7

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.

mqp
  • 70,359
  • 14
  • 95
  • 123
  • I think I'm having a brain fart. Why does C# allow me to cast if the types are not compatible? Normally it forbids invalid casts. :/ – GazTheDestroyer Dec 06 '11 at 16:04
  • 6
    @Gaz No, it doesn't. Explicit casts are pretty much always legal in the compiler because you're implying "Trust me, I know what I'm doing". They then blow up at runtime. Implicit casts are caught by the compiler, as you saw on the CS0266 above. – Michael Stum Dec 06 '11 at 16:07
5

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.

Azhar Khorasany
  • 2,712
  • 16
  • 20