3

Suppose I have a function

DoSomething(Dictionary<object, object> dict)

why can't it be called with

DoSomething(new Dictionary<int, string>())?

Since intand string both inherit from object, I'd expect this to work. I know I could extend this to

DoSomething<T1, T2>(Dictionary<T1, T2> dict)

, but why won't the first option work?

Suraj Singh
  • 4,041
  • 1
  • 21
  • 36
Janis F
  • 2,637
  • 1
  • 25
  • 36

5 Answers5

4

It is because a Dictionary<int, string> is not a Dictionary<object, object>.

For that to be true, the class type Dictionary<TKey, TValue> would need to be covariant in both TKey and TValue, and covariance would need to work with value types (boxing).

First of all, in .NET, generic class types are always invariant. Only interface types and delegate types can be co- or contravariant.

Secondly, a Dictionary<,> is not semantically covariant. For example you could say:

myDict.Add(new Elephant(), new BankCustomer());

and that would not be very pleasant if myDict was actually a (run-time) Dictionary<int, string> in a Dictionary<object, object> variable.

Now, since .NET 4.5, some of the "non-dictionary" types implement covariant interfaces like IReadOnlyList<out T>. You might hope that the similar interface IReadOnlyDictionary<TKey, TValue> was covariant too. But it is not (the reason being given by the first comment by Servy below). So there is no hope for you.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 1
    `IReadOnlyDictionary` *must* be invariant with respect to `TValue` because `TryGetValue` uses it as an `out` parameter, which cannot vary in the type used. `TKey` must be invariant because the key is used as input using `ContainsKey` and as output through `Keys`. If `TryGetValue` had its signature changed so that `TValue` was only used in output then that generic argument could be covariant. – Servy Dec 16 '13 at 15:51
  • @Servy Absolutely! I mentioned your comment in the answer. Also note that in order for `IReadOnlyList` to be covariant in `T`, they had to omit the method `int IndexOf(T item);` which is otherwise present in list interfaces. – Jeppe Stig Nielsen Dec 16 '13 at 15:54
  • @Servy Upon thinking a bit more, I realize that what you very often do with a `IReadOnlyDictionary<,>` is to `foreach` through it. There is the `GetEnumerator` method whose return type is also important for the variance here. For `foreach` to work, they would need to introduce some `IReadOnlyKeyValuePair` or something like that. – Jeppe Stig Nielsen Dec 16 '13 at 16:05
2

Just because B is a subtype of A, doesn't mean List<B> is a subtype of List<A> (or dictionaries, doesn't matter).

If it was, this would be legal at compile-time, but cause a runtime exception:

List<A> aList = new List<B>();
aList.Add(new C());

where C is a subtype of A.

For X<B> to be a subtype of X<A>, then X<T> would have to be covariant in its type parameter T. For X to be covariant, T would have to be used only as the return type of its methods, but never as an argument.

Take IEnumerable<T> for example - this is a covariant interface. IEnumerable<string> is a subtype of IEnumerable<object>, because T is only used as a return type for GetEnumerator. So it's legal at both compile and runtime to do this:

IEnumerable<object> enumerator = new List<string>();
foreach(object o in enumerator)
{
    Console.WriteLine(o);
}

More about covariance and contravariance in C#: http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

dcastro
  • 66,540
  • 21
  • 145
  • 155
2

Because Dictionary is invariant with respect to the types of both generic argument.

In C# all generic arguments are invariant. Generic arguments can only be covariant or contravariant for interfaces.

The IDictionary interface also could not be covariant or contravariant with respect to either type argument. Keys are passed out through the Keys property, in addition to being passed in...all over, and values are passed in using Add in addition to being passed out...all over. Because both types are used as both input and output, the types must be invariant.

That, and covariance/contravariance will not work with value types; it can only work with reference types.

Just to provide a simple example, if you were allowed to make such a cast then I would be able to call dict.Add("not an int", new Foo()) on the Dictionary<object, object>. I've now added a key that's not an int and a value that's not a string. Because allowing the cast would allow the use of types that don't match the signatures, it doesn't work.

Servy
  • 202,030
  • 26
  • 332
  • 449
1

You can use it because the type isn't covariant or contravariant in this way. Take this example:

var dict=new Dictionary<int,string>();
DoSomething(dict);

DoSomething(Dictionary<object, object> dict)
{
  var now=DateTime.Now;
  dict[now]=now;
}

In your scenario you've now added a DateTime to a dictionary of [int,string]!

Sean
  • 60,939
  • 11
  • 97
  • 136
0

Simply because Dictionary<T,Q> is not covariant.

If it was possible then the following line would throw a runtime exception;

Dictionary<object, object> dict = new Dictionary<string, string>();
dict["hello"] = new SomeOtherObject();

Since you would not be warned during build, you would end up with intermittent errors.

daryal
  • 14,643
  • 4
  • 38
  • 54