为啥 List.AsReadOnly 返回 ReadOnlyCollection 而 Dictionary.AsReadOnly 返回 IReadOnlyDictionary?
Posted
技术标签:
【中文标题】为啥 List.AsReadOnly 返回 ReadOnlyCollection 而 Dictionary.AsReadOnly 返回 IReadOnlyDictionary?【英文标题】:Why does List.AsReadOnly return a ReadOnlyCollection but Dictionary.AsReadOnly returns an IReadOnlyDictionary?为什么 List.AsReadOnly 返回 ReadOnlyCollection 而 Dictionary.AsReadOnly 返回 IReadOnlyDictionary? 【发布时间】:2016-11-20 00:35:24 【问题描述】:我遇到了这样一种情况,List.AsReadOnly()
返回 ReadOnlyCollection
而不是 IReadOnlyCollection
让我很难过。由于返回的集合是Dictionary
的Value
,因此无法自动向上转换为IReadOnlyCollection
。这看起来很奇怪,在查看 .Net 源代码后,我确认AsReadOnly()
方法对List
的作用与对Dictionary
的作用不同,即返回具体类而不是接口。
谁能解释这是为什么?出现这种不一致似乎是一种伤害,尤其是因为我们希望尽可能使用这些接口,尤其是在公开的情况下。
在我的代码中,起初我在想,由于我的消费者只是一个私有方法,我可以将其参数签名从IReadOnlyDictionary<T, IReadOnlyCollection<T>>
更改为IReadOnlyDictionary<T, ReadOnlyCollection<T>>
。但是,后来我意识到这使得私有方法看起来可能会修改集合值,所以我在前面的代码中加入了一个烦人的显式转换,以便正确使用接口:
.ToDictionary(
item => item,
item => (IReadOnlyCollection<T>) relatedItemsSelector(item)
.ToList()
.AsReadOnly() // Didn't expect to need the direct cast
)
哦,由于我总是混淆协变和逆变,有人可以告诉我哪个阻止了自动转换,并尝试以明智的方式提醒我如何在未来记住它们? (例如,对于_____ [输入/输出] 参数,集合不是 __variant [co/contra]。)我理解为什么不能这样,因为接口可以有很多实现,并且将所有的字典的各个元素到请求的类型。除非我连这个简单的方面都吹了而且我不理解它,在这种情况下,我希望你能帮助我纠正...
【问题讨论】:
【参考方案1】:原因是历史性的。 IReadOnly
* 接口是在 .NET 4.5 中添加的,而 List<T>.AsReadOnly()
是在 .NET 2.0 中添加的。更改其返回类型将是一项重大更改。
显式演员表还不错。它甚至不是运行时强制转换,因为编译器可以静态验证它(不会向 IL 发出强制转换)。顺便说一句,您可以将其转换为IReadOnlyList<T>
,它还提供对列表的索引访问。你也可以编写一个扩展方法来返回你需要的类型(例如AsReadOnlyList()
)。
关于 co,contravariance,我发现使用 C# 关键字 in
(逆变)和 out
(协变)更容易记住。 in
类型参数只能作为输入 方法参数出现,而out
类型参数只能作为输出(返回值)出现。接受参数的方法,例如Base
类型的,使用 Derived
类型调用是安全的,因此在该方向上转换 in
参数是安全的。 out
正好相反。
例如:
interface IIn<in T> Set(T value);
IIn<Base> b = ...
IIn<Derived> d = b;
d.Set(derived); // safe since any method accepting Base can handle Derived
interface IOut<out T> T Get();
IOut<Derived> d = ...
IOut<Base> b = d;
b.Get(); // safe since any Derived is Base
非只读集合接口不能是 *-variant,因为它们必须同时是 in
和 out
,这是不安全的。编译器和 CLR 不允许这样做。 .NET 确实有一种不安全的数组变化形式:
var a = new[] "s" ;
var o = (object[])a;
o[0] = 1; // ArrayTypeMismatchException
您可以看到他们是如何避免出现泛型差异带来的混乱。大概他们可以添加允许in
(协变)方向的只写接口,但我猜他们并没有从中找到很多价值。
【讨论】:
哦哦哦!我假设它不会自动转换,因为它 不是 协变的,但现在我看到正是因为IReadOnlyCollection
是只读的,编译器可以看到从派生类型到基类。那么如果是这样的话,为什么我必须进行显式转换?
协变和逆变只发生在两个通用接口之间转换并改变类型参数(从基础到派生,反之亦然) .您使用的演员表只是演员表 - 这是允许的,因为 ReadOnlyCollection<T>
实现了 IReadOnlyList<T>
。
哦,哎呀,有道理。越来越清楚了。为什么不能隐式转换?
强制转换是隐式的,例如你可以写IReadOnlyList<object> x = list.AsReadOnly()
,但在你的例子中,编译器只是推断出 lambda 的类型并将其用于ToDictionary
方法。同样,与协方差无关 - 您没有更改任何类型参数。以上是关于为啥 List.AsReadOnly 返回 ReadOnlyCollection 而 Dictionary.AsReadOnly 返回 IReadOnlyDictionary?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 npm install react-native 不起作用?
在 JavaScript Discord Bot 中,为啥 message.react() 是“不是函数”