5

I can't for the life of me figure this out. Say I have the following two dictionary objects:

// Assume "query" is a LINQ queryable.
Dictionary<string, int> d1 = query.ToDictionary(k => k.Key, v => v.Value);
Dictionary<string, int>  d1 = query.ToDictionary(k => k.Key, v => v.Value);

The following statement produces a compile time error that an implicit conversion between a Dictionary and IDictionary is not possible:

// Compile time error!
Tuple<IDictionary<string, int>, IDictionary<string, int>> = Tuple.Create(d1, d2);

I have to do the conversion explicitly:

Tuple<IDictionary<string, int>, IDictionary<string, int>> = Tuple.Create(d1 as IDictionary<string, int>, d2 as IDictionary<string, int>);

I am not understanding why the compiler can't figure out the covariance operation - Dictionary implements IDictionary - especially since something like this will of course work as we all know:

IDictionary<string, int> d3 = d1;

I am sure there is a good reason for this behavior and I am curious what it is.

Update 1: Just to clarify, I am curious about the behavior not how to solve the problem. I am aware of the different solutions :)

Update 2: Thank you all for the great answers. I did not know Tuple was invariant and now I do.

9ee1
  • 1,078
  • 1
  • 10
  • 25

5 Answers5

7

Basically, the problem is that the Tuple family isn't covariant in its type arguments. It can't be, because it's a class. An interface or delegate version could be created that would be covariant, however, as there are no members accepting the type parameters in input positions.

This is simplest to see with Tuple<T1>:

Tuple<string> stringTuple = Tuple.Create("Foo");
Tuple<object> objectTuple = stringTuple;

This call:

Tuple.Create(d1, d2);

... is inferring both type arguments as Dictionary<string, int>, so you're trying to convert from Tuple<Dictionary<string, int>, Dictionary<string, int>> to Tuple<IDictionary<string, int>, IDictionary<string, int>>, which doesn't work.

The version with as changes the argument types so that type inference gives the desired type arguments - but it would be simpler to just write the type arguments directly, and avoid inference entirely, as per Sebastian's answer:

Tuple.Create<IDictionary<string, int>, IDictionary<string, int>>(d1, d2)

If you use var at this point, it's not so bad:

var x = Tuple.Create<IDictionary<string, int>, IDictionary<string, int>>(d1, d2);

The type of x will now be Tuple<IDictionary<string, int>, IDictionary<string, int>> which is what you want.

EDIT: As noted in comments, you might as well just use the constructor at that point:

var x = new Tuple<IDictionary<string, int>, IDictionary<string, int>>(d1, d2);
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thank you for the detailed answer. All the answers were great, but I am marking this one as the answer because of the details given. – 9ee1 Nov 11 '12 at 17:17
  • @agarhy Of course, when (like here) you need to explicitly specify the type arguments `T1` and `T2` to the static `Tuple.Create` method, you might as well just use the ordinary instance constructor of the generic type `Tuple` with a new-object expression: `new Tuple(d1, d2)` – Jeppe Stig Nielsen Nov 26 '12 at 21:01
3

You are trying to assign...

Tuple<Dictionary<string, int>, Dictionary<string, int>>

...to...

Tuple<IDictionary<string, int>, IDictionary<string, int>>

...but the Tuple<T1, T2> is a class and therefore invariant.

Community
  • 1
  • 1
Branko Dimitrijevic
  • 50,809
  • 10
  • 93
  • 167
2

The type parameters for Tuple.Create are inferred from the types and thus the right hand side is a Tuple of two Dictionaries and this is a different type than Tuple of IDictionarys - if you explicitly add the type parameters to Tuple.Create to be of type IDictionary everything should work as expected because it is OK for the method parameters to be of a more specific type.

 Tuple.Create<IDictionary<string, int>,IDictionary<string, int>>(d1,d2)
Sebastian
  • 7,729
  • 2
  • 37
  • 69
2

There is no conversion between a Tuple<Dictionary<string, int>, Dictionary<string, int>> and a Tuple<IDictionary<string, int>, IDictionary<string, int>> since classes in C# (and therefore the Tuple<T1, T2> class) do not support covariance.

The compiler has inferred the most specific type of the return type for your call to Tuple.Create, and does not use the type on the left-hand side during inferrence.

Lee
  • 142,018
  • 20
  • 234
  • 287
0

Try changing the variable type from Dictionary<string, int> to IDictionary<string, int>.

IDictionary<string, int> d1 = query.ToDictionary(k => k.Key, v => v.Value);
IDictionary<string, int>  d1 = query.ToDictionary(k => k.Key, v => v.Value);
Panos Rontogiannis
  • 4,154
  • 1
  • 24
  • 29
  • That would work. But I am curious about the behavior, because if I use an implicit typed variable declaration, such as this, `var d1 = query.ToDictionary(k => k.Key, v => v.Value);` it won't work. I guess the question is, why do I have to do the conversion manually? – 9ee1 Nov 11 '12 at 16:54
  • By using var, the compiler will set the type as Dictionary because Enumerable.ToDictionary returns Dictionary and not IDictionary. – Panos Rontogiannis Nov 11 '12 at 16:58
  • You're right. I mis-typed what I meant. I meant to ask why passing `query.ToDictionary(k => k.Key, v => v.Value)` won't work. Thanks for your answer. – 9ee1 Nov 11 '12 at 17:07