Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot Convert Dictionary to IDictionary in a Method Call

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.

like image 691
9ee1 Avatar asked Nov 11 '12 16:11

9ee1


1 Answers

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);
like image 162
Jon Skeet Avatar answered Oct 08 '22 03:10

Jon Skeet