为什么 `IList<T>` 没有继承自 `IReadOnlyList<T>`?

44

IReadOnlyList<T> 在 .NET 4.5 中被引入时,我曾一度认为拼图上缺失的部分终于插入到了正确的位置:一种传递真正只读可索引接口的方式,在此之前我必须使用自己的只读接口并在所有内容周围创建包装类。

我期望该接口被放置在“自然”的层次结构中,这理想情况下将是:

IEnumerable<T> 
.GetEnumerator()
      -> IReadOnlyCollection<T> : IEnumerable<T>
      .Count
            -> IReadOnlyList<T> : IReadOnlyCollection<T>
            .Item[...]
                     -> IList<T> : IReadOnlyList<T>
                     .Add(...)
                     .Clear()
                     .Contains(...)
                     (etc)

但是事实证明, IList<T> 不继承IReadOnlyList<T>

这样做有原因吗?

一些澄清:

请注意,IReadOnlyList<T> 只是一个契约,指出列表提供了一种获取列表计数并读取特定索引处的值的方法。它的命名不够准确,因为它并没有强制执行实际实现是只读的。

List<T> 实现了 IEnumerable<T>,而 IList<T> 继承自 IEnumerable<T>,但这并不意味着这些类可以被枚举。

因此,如果您想将列表传递给一个方法,并且只允许对其进行索引(读取),但不允许修改,则需要将其包装在新实例中。同时,您可以将其传递给接受IEnumerable<T>IList<T>的方法而不必将其包装起来。这就是我认为有问题的地方。

我还相信应该为 IReadOnlyCollection 命名为 ICountable,为 IReadOnlyList 命名为 IIndexable

IEnumerable<T> 
.GetEnumerator()
      -> ICountable<T> : IEnumerable<T>
      .Count
            -> IIndexable<T> : ICountable<T>
            .Item[...]
                     -> IList<T> : IIndexable<T>
                     .Add(...)
                     .Clear()
                     .Contains(...)
                     (etc)

7
正如你已经说的,“IReadonlyList”是在.NET 4.5中引入的,比添加到.Net中的“IList”要晚得多。 - MakePeaceGreatAgain
4
请查看:https://social.msdn.microsoft.com/Forums/vstudio/en-US/b4fb991a-3f5c-4923-93d4-7cd5c004f859/new-interfaces-ireadonlylist-and-ireadonlydictionary?forum=netfxbcl这篇文章介绍了.NET Framework 4.5中新增加的两个接口:IReadOnlyList和IReadOnlyDictionary。这些接口提供了只读列表和只读字典的功能,可以用于保护集合内容不被意外修改。同时,讨论中还提到了其他几个相关的接口和类。 - w.b
6
List<> 实现了 IList<>,但它不是只读的,那么它为什么要实现 IReadOnlyList<> 呢? - Tim Schmelter
14
@TimSchmelter List<> 实际上实现了 IReadOnlyList<>。而这个问题并不是关于 List<>,而是关于 IList<> - user4003407
10
这真的很烦人,因为我不能只在IReadOnlyList<T>上编写扩展方法,而且我也不能同时为IList<T>IReadOnlyList<T>编写它们,否则会出现模糊的方法错误。呃。 - Mike Marynowski
显示剩余6条评论
1个回答

48

@w.b在评论中放置一个链接新接口IReadOnlyList和IReadOnlyDictionary,其中包含答案:

为什么我们没有更改现有的接口以扩展只读接口?

看起来这是一个合理的假设,因为只读接口纯粹是读写接口的子集。不幸的是,这是不兼容的,因为在元数据级别上,每个接口上的每个方法都有自己的槽位(这使得显式接口实现生效)。


Immo Landwerth | .NET Framework团队(BCL)| http://blogs.msdn.com/b/bclteam/

为了更清楚地解释这一点:

假设一个为.NET 4.0编写的程序包含一个实现了IList<T>接口的类MyList<T>。显然,它无法实现IReadOnlyList<T>接口,因为该接口不存在。
现在假设系统管理员安装了.NET 4.5,并且.NET 4.5使IList<T>接口实现了IReadOnlyList<T>接口。
如果加载该程序,则运行时会检测到MyList<T>声称实现了IList<T>接口,但实际上并没有实现所有方法:它没有实现IReadOnlyList<T>接口的方法。该程序将不再工作。
C#编译器可能能够通过名称匹配方法,但运行时不会这样做。由于.NET 4.5应该具有向后二进制兼容性,因此不能扩展接口以实现其他接口,即使这些其他接口包含所需方法的严格子集。

4
另一个例子涉及到引用显式实现。如果一个类已经有了显式的 int IList<T>.Count ... 实现,并且 IList<T> 要继承自 IReadOnlyList<T>,则 Count 将属于后者,即使使用正确的框架构建,现有的实现也会失败。 如果一个类已经有了显式的 int IList<T>.Count... 实现,而该类要继承自 IReadOnlyList<T> 接口时,Count 属性将属于后者,即使使用了正确的框架来构建,现有的实现也会失败。请注意,这里提到的是“显式”实现,它是一种实现接口成员的方法,其中成员名称前缀带有接口名称,例如 IList<T> - Me.Name
2
.NET 5.x 是否愿意打破ABI并修复此问题? - binki
2
据我所知,本来应该是.NET 5的已经被重新命名为.NET Core(如果我错了,有人可以纠正我,告诉我实际上是其他什么东西)。在.NET Core中,IList<T>目前仍未实现IReadOnlyList<T> - user743382
2
@hvd:我认为.NET Core的一个目标是尽可能保持API兼容性,以便尽可能轻松地迁移代码(如果不是二进制文件)到/从完整框架,但我不确定... - Joe Amenta
在.NET中,IList<T>IReadOnlyList<T>以及ICollection<T>IReadOnlyCollection<T>的区别有些混乱。请参见此链接 - Shimmy Weitzhandler
显示剩余4条评论

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