为什么将IDictionary<TKey,TValue>向上转换为IEnumerable<object>会失败?

20

请看下面的代码片段:

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

上述转换将会抛出一个无效转换异常。

实际上,IDictionary<TKey, TValue> 也间接实现了 IEnumerable<out T> 接口,因为它还实现了 ICollection<T> 接口。也就是说,整个转换应该是有效的。

事实上,对我来说更奇怪的是,如果我在调试器的 watch 中运行整个转换,它可以正常工作

输入图像描述

发生了什么?


1
@PatrickHofman 为什么不行呢?只有一个名为 IEnumerable<T> 的接口具有协变类型参数。即使协变无法与值类型一起使用.............我错了吗? - Matías Fidemraizer
@CodeCaster 我的意思是接口本身是 IEnumerable<out T> - Matías Fidemraizer
@CodeCaster 哎呀,https://msdn.microsoft.com/zh-cn/library/9eekhta0(v=vs.110).aspx - Matías Fidemraizer
@CodeCaster 你知道我们在谈论什么。我知道 IEnumerable<string>IEnumerable<int> 不是同一种类型,但当你实现它们时,你正在实现 IEnumerable<out T> - Matías Fidemraizer
太好了,那就解决了。我已经厌倦了在 MSDN 上点击继承树了。 ;) - CodeCaster
为什么在“监视”窗口中可以正常工作 - 来自Eric Lippert的文章,当编译器看到以下代码时:short s; var o = (object)s; var i = (int)o;它无法确定o中实际上是一个short,唯一能使其正常工作的方法是在运行时启动一个小型编译器实例来查找适当的转换方法。我的理解是,VS的调试功能已经在使用它们自己的编译器实例,因此这种限制没有任何好处。 - Zev Spitz
4个回答

25
该字典确实实现了 IEnumerable<KeyValuePair<TKey, TValue>>IEnumerable,但是结构体的 IEnumerable 与对象的 IEnumerable 不同。 协变仅适用于引用类型。 KeyValuePair<K, V> 是一个结构体而不是类。
这在我的端口上工作,并且应该是逻辑上的选择:
var x = (IEnumerable)new Dictionary<string, string>();

作为一个例子,这确实起作用:

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

但这个不行:

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

明确指出结构体是问题所在。

(为什么它能够在您的监视窗口中工作,我不知道)


但是 IEnumerable<T> 是协变的! - Matías Fidemraizer
这并没有解释为什么带有相同铸造的手表已经可以工作了! - Matías Fidemraizer
6
相关:"方差只适用于引用类型" - CodeCaster
1
@PatrickHofman 这就是让我疯掉并想要问这个问题的原因,哈哈。 - Matías Fidemraizer
1
其实我必须承认我的错误:我从来没有要求值类型的协变,这导致我对无法执行整个转换一无所知。 - Matías Fidemraizer
显示剩余4条评论

4
我认为这是因为 KeyValuePair 是一个值类型。
这个会失败:
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;

同样适用于字典。

你说得完全正确。目前我们应该接受Patrick的答案,但如果像Eric Lippert这样的人能回答为什么调试器比我们更强大,那就太好了! - Matías Fidemraizer
我猜那只是调试工具中的解析错误。我不认为它们真的会即时编译语句并进行评估。@MatíasFidemraizer - Patrick Hofman
1
@PatrickHofman 是的,应该是那样...顺便说一下,这可能被认为是一个bug:调试器与实际运行时的行为不完全一致 :\ - Matías Fidemraizer
@MatíasFidemraizer 一方面,你是正确的。另一方面……当你意识到调试器完全不能使用 .NET 框架(你正在调试的进程已经被暂停,包括该进程的整个 .NET 运行时),它仍然非常接近,这是相当惊人的。在 LISP 机器上,“调试”仍然会留下“运行时”的所有功能,做本地调试也是如此(但本地调试器通常比 .NET 的差)。而 .NET 的调试器两者都做不到。我希望他们能修复每一个像这样的小错误,但实际上我对他们并没有抱怨的余地 :D - Luaan
@Luaan 你说得对。毕竟,软件是由像我们这样的人完成的,我们无法覆盖世界上的每一个角落案例。 - Matías Fidemraizer

1

如果您实在无法使用普通的IEnumerable,请尝试以下方法:

new Dictionary<string, string>().Cast<object>();

这不是问题。楼主肯定知道这一点。他只是想知道为什么会发生这种情况。 - Patrick Hofman
是的,完全正确。问题在于调用 Enumerable.Cast 会产生一个新的可枚举对象,而我需要保持相同的实例并将其转换为整个类型实现的不同接口。 - Matías Fidemraizer
你能够对 Dictionary<string, string> 进行子类化吗?如果可以的话,你就可以让它实现 IEnumerable<object> - Paulo Morgado

0

这很奇怪。您的代码片段在 .NET 4.0 中运行良好。我建议您将其转换为 IEnumerable

阅读:IDictionary Interface(MSDN)


1
这甚至更加好笑,不是吗? - Matías Fidemraizer
1
你确定吗?我已经尝试过了,它会抛出相同的异常。 - Matías Fidemraizer
确定了,它可以在.net4.0下运行,但在.net4.5下失败了。 无论如何,Patrick Hofman和user3185569都做得很好。根据MSDN,它实现了IEnumerable<KeyValuePair<TKey, TValue>>,而不是IEnumerable<out T>,因为KeyValuePair是一个结构体。我建议我们接受Patrick Hofman的答案。 - Olivier Citeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接