为啥 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 让我很难过。由于返回的集合是DictionaryValue,因此无法自动向上转换为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&lt;T&gt;.AsReadOnly() 是在 .NET 2.0 中添加的。更改其返回类型将是一项重大更改。

显式演员表还不错。它甚至不是运行时强制转换,因为编译器可以静态验证它(不会向 IL 发出强制转换)。顺便说一句,您可以将其转换为IReadOnlyList&lt;T&gt;,它还提供对列表的索引访问。你也可以编写一个扩展方法来返回你需要的类型(例如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,因为它们必须同时是 inout,这是不安全的。编译器和 CLR 不允许这样做。 .NET 确实有一种不安全的数组变化形式:

var a = new[]  "s" ;
var o = (object[])a;
o[0] = 1; // ArrayTypeMismatchException

您可以看到他们是如何避免出现泛型差异带来的混乱。大概他们可以添加允许in(协变)方向的只写接口,但我猜他们并没有从中找到很多价值。

【讨论】:

哦哦哦!我假设它不会自动转换,因为它 不是 协变的,但现在我看到正是因为 IReadOnlyCollection 是只读的,编译器可以看到从派生类型到基类。那么如果是这样的话,为什么我必须进行显式转换? 协变和逆变只发生在两个通用接口之间转换并改变类型参数(从基础到派生,反之亦然) .您使用的演员表只是演员表 - 这是允许的,因为 ReadOnlyCollection&lt;T&gt; 实现了 IReadOnlyList&lt;T&gt; 哦,哎呀,有道理。越来越清楚了。为什么不能隐式转换? 强制转换是隐式的,例如你可以写IReadOnlyList&lt;object&gt; x = list.AsReadOnly(),但在你的例子中,编译器只是推断出 lambda 的类型并将其用于ToDictionary 方法。同样,与协方差无关 - 您没有更改任何类型参数。

以上是关于为啥 List.AsReadOnly 返回 ReadOnlyCollection 而 Dictionary.AsReadOnly 返回 IReadOnlyDictionary?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Vue.js 使用 VDOM?

为啥这个 Python“循环代码”不起作用?

为啥 npm install react-native 不起作用?

在 JavaScript Discord Bot 中,为啥 message.react() 是“不是函数”

用 jquery 前置 html 的一种方法有效,但为啥另一种方法无效?

为啥不使用 sf_read_double 读取此音频文件?