Reputation: 21261
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
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
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
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