Why does upcasting IDictionary<TKey, TValue> to IEnumerable<object> fail?
Asked Answered
M

4

20

See the following code snippet:

(IEnumerable<object>)new Dictionary<string, string>()

The above cast will throw an invalid cast exception.

Actually, IDictionary<TKey, TValue> also indirectly implements IEnumerable<out T>, because it also implements ICollection<T>. That is, the whole cast should be valid.

In fact, for me it is even more strange that if I run the whole cast on a debugger watch slot, it works!

Enter image description here

What's going on?

Misname answered 18/7, 2016 at 9:32 Comment(6)
@PatrickHofman Why it doesn't? There's only one interface called IEnumerable<T> which has a covariant type parameter. Even when covariance won't work with value types.............Am I mistaken?Leonoreleonsis
@CodeCaster I mean that the interface itself is IEnumerable<out T>Leonoreleonsis
@CodeCaster Argh, msdn.microsoft.com/en-us/library/9eekhta0(v=vs.110).aspxLeonoreleonsis
@CodeCaster You know what we're talking about. I know that IEnumerable<string> isn't the same type as IEnumerable<int> but when you implement these, you're implementing IEnumerable<out T>Leonoreleonsis
Great, that clears that up then. I got tired clicking through the inheritance tree on MSDN. ;)Pettit
Why this works in the Watch window - From Eric Lippert's article, when the compiler sees following code: short s; var o = (object)s; var i = (int)o; it cannot determine that what is actually in o is a short, and the only way for this to work would be to spin up at runtime a small instance of the compiler to find the appropriate conversion method. My understanding is the debugging features of VS already are using their own instance of the compiler, so there is no benefit in this limitation.Semela
A
25

That dictionary does implement IEnumerable<KeyValuePair<TKey, TValue>> and IEnumerable, but IEnumerable of a struct is not the same as IEnumerable of an object. Variance only works for reference-types. KeyValuePair<K, V> is a struct and not a class.

This does work at my end and should be the logical choice:

var x = (IEnumerable)new Dictionary<string, string>();

As a sample, this does work:

List<string> l = new List<string>();
var x = (IEnumerable<object>)l;

But this one doesn't:

List<DateTime> l2 = new List<DateTime>();
var x = (IEnumerable<object>)l2;

Clearly indicating the struct is the problem.

(Why it works in your Watch windows, I don't know)

Associationism answered 18/7, 2016 at 9:34 Comment(9)
But IEnumerable<T> is covariant!Leonoreleonsis
And this doesn't explain why a watch with the same cast already works!Leonoreleonsis
Because KeyValuePair<K, V> is a struct and not a class?Associationism
Relevant: "Variance only works for reference-types"Pettit
@Pettit This should be the answerLeonoreleonsis
@PatrickHofman I would simplify your answer to just that citationLeonoreleonsis
BTW as you already said in your answer, it would be good to know why the debugger can perform the cast. Actually, my question was about that surprise. It seems like it does magic, isn't it?Leonoreleonsis
@PatrickHofman This is what has turned me crazy and I wanted to ask this question lolLeonoreleonsis
Actually I've to recognize my fault: I've never required covariance with value types and this has driven to my ignorance about not being able to perform the whole castLeonoreleonsis
B
4

I think this is because KeyValuePair is a Value-Type.

This would fail:

List<int> ints = new List<int>();
var objs = (IEnumerable<object>)ints;

This would work:

List<string> ints = new List<string>();
var objs = (IEnumerable<object>)ints;

same goes for the dictionary.

Bowl answered 18/7, 2016 at 9:39 Comment(5)
You're absolutely right. For now we should accept Patrick's answer, but it would be great that someone like Eric Lippert could answer us why the debugger has more power than us!Leonoreleonsis
I guess that is just a parse bug in the debugging tools. I don't think they really compile the statement on the fly and evaluate it then. @MatíasFidemraizerAssociationism
@PatrickHofman Yeah, it should be that... BTW it could be considered as a bug: debugger isn't 100% coherent with actual runtime behavior :\Leonoreleonsis
@MatíasFidemraizer On one hand, you are right. On the other... it's still quite impressive how close the debugger gets, when you realize that it can't use the .NET framework at all (the process you are debugging is suspended, and that includes the whole .NET runtime for that process). On a LISP machine, "debugging" still left you every faculty of the "runtime"; the same is true when doing native debugging (and yet, native debuggers usually suck compared to .NET's); .NET debugger can't do either. I hope they'll fix every tiny bug like this, but can't really hold it against them :DReticular
@Reticular You're right. After all, software is done by people like us and we can't cover every corner case in the world.Leonoreleonsis
H
1

If you really can't live with plain IEnumerable, try this:

new Dictionary<string, string>().Cast<object>();
Hickson answered 18/7, 2016 at 11:14 Comment(3)
That isn't the question. OP knows that for sure. He just wants to know why this happens.Associationism
Yeah, absolutely. The problem is calling Enumerable.Cast produces a new enumerable and I need to hold the same instance and just cast it to different interfaces implemented by the whole typeLeonoreleonsis
Can you subclass Dictionary<string, string>? If you can, you can make it implement IEnumerable<object>.Hickson
A
0

This is strange. Your code snippet works fine with .NET 4.0. I suggest you cast to IEnumerable.

Read: IDictionary Interface (MSDN)

Acaudal answered 18/7, 2016 at 9:50 Comment(3)
This is even more LOL, isn't it?Leonoreleonsis
Are you sure about that? I've already tried it myself and it throws the same exceptionLeonoreleonsis
Sure about that, it works with .net4.0 and fails with .net4.5. Anyway, Patrick Hofman and user3185569 nailed it. From the MSDN, it implements IEnumerable<KeyValuePair<TKey, TValue>>, NOT IEnumerable<out T>, since KeyValuePair is a struct. I suggest we accept Patrick Hofman's answerAcaudal

© 2022 - 2024 — McMap. All rights reserved.