为啥泛型 ICollection 不在 .NET 4.5 中实现 IReadOnlyCollection?

Posted

技术标签:

【中文标题】为啥泛型 ICollection 不在 .NET 4.5 中实现 IReadOnlyCollection?【英文标题】:Why doesn't generic ICollection implement IReadOnlyCollection in .NET 4.5?为什么泛型 ICollection 不在 .NET 4.5 中实现 IReadOnlyCollection? 【发布时间】:2012-09-19 07:22:37 【问题描述】:

在 .NET 4.5 / C# 5 中,IReadOnlyCollection<T> 使用 Count 属性声明:

public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable

    int Count  get; 

我想知道,ICollection&lt;T&gt; 也实现 IReadOnlyCollection&lt;T&gt; 接口是否有意义:

public interface ICollection<T> : IEnumerable<T>, IEnumerable, *IReadOnlyCollection<T>*

这意味着实现ICollection&lt;T&gt; 的类会自动实现IReadOnlyCollection&lt;T&gt;。这对我来说听起来很合理。

ICollection&lt;T&gt; 抽象可以看作是IReadOnlyCollection&lt;T&gt; 抽象的扩展。请注意,例如 List&lt;T&gt; 实现了 ICollection&lt;T&gt;IReadOnlyCollection&lt;T&gt;

但是它并不是这样设计的。

我在这里缺少什么?为什么会选择当前的实现?


更新

我正在寻找一个使用面向对象设计推理来解释原因的答案:

一个具体的类,例如 List&lt;T&gt; 实现 IReadOnlyCollection&lt;T&gt; ICollection&lt;T&gt;

比以下更好的设计:

ICollection&lt;T&gt; 直接实现IReadOnlyCollection&lt;T&gt;

另外请注意,这基本上与以下问题相同:

    为什么IList&lt;T&gt; 不实现IReadOnlyList&lt;T&gt;? 为什么IDictionary&lt;T&gt; 不实现IReadOnlyDictionary&lt;T&gt;

【问题讨论】:

这将如何影响向后兼容性? @asawyer 就我所见,它没有……除非你能提供一个反例? @asawyer 他们特别决定做出重大改变,将只读相关接口添加到 .NET 的集合中。 (知道实际会破坏代码的情况是相当人为的,不太可能在生产代码中普遍存在。) @ZaidMasud 好吧,在技术层面上,几乎所有事情都是一个突破性的变化。例如,通过添加新接口可能会影响方法重载决议。 我仍然不确定哪一方的论点最好。在多次换边之后,现在我倾向于“ICollection&lt;T&gt; 不应该实现IReadOnlyCollection&lt;T&gt;List&lt;T&gt; 实现IReadOnlyList&lt;T&gt; 没有意义”。更大的问题是当类C 实现接口I 时,这是否意味着C 可以IC 应该是I。在此处查看等效问题:why-does-listt-implement-ireadonlylistt-in-net-4-5。发人深省的问题顺便说一句,+1! 【参考方案1】:

当我第一次阅读您的问题时,我想“嘿,他是对的!那是最有意义的。”但是接口的重点是描述契约——行为的描述。如果你的类实现了IReadOnlyCollection,它应该是只读的。 Collection&lt;T&gt; 不是。因此,Collection&lt;T&gt; 实现一个隐含只读的接口可能会导致一些非常讨厌的问题。 IReadOnlyCollection 的消费者将对底层集合做出某些假设——比如迭代它是安全的,因为没有人可以修改它等等。如果它的结构像你建议的那样,那可能不是真的!

【讨论】:

List&lt;T&gt; 等集合类已被修改为同时实现 IReadOnlyCollection 和 ICollection... 那么当前设计中 List 的消费者不会遇到与您所说的相同的问题吗? 说得很好,扎伊德。事实上,最后一行:visualstudiomagazine.com/articles/2012/08/07/… 暗示 BEHAVIOR 不是实现或使用接口的标准! 对于不承诺不可变的类型IReadableCollection&lt;out T&gt; 或承诺永远包含同一组T 的接口IImmutableCollection&lt;T&gt; 会有很大的用处。对于承诺对象是“只读”但不保证它封装不可变序列的接口,我想不出太多用处。 IReadOnlyCollection&lt;T&gt; 用于本应称为 IReadableCollection&lt;T&gt; 的功能。 普通IReadOnlyCollection&lt;T&gt; 的全部意义在于您可以将不可变列表和可变列表都视为只读列表。它允许您定义消费者只需要读取一个索引值的 lsit 与每种类型的列表一起使用,因为即使是可变列表也能够在您通过IReadOnly 接口访问它们时保持静态。为什么不希望能够将可变列表传递给一个函数,该函数以只读索引列表作为输入来执行一些有用的任务? 这个答案很不合理。任何实现 IReadOnlyCollection 的类都实现了足够的 API 以将其视为只读集合。如果通过IReadOnlyCollection 类型的引用访问,您将只能将其用作只读集合。无论是实现进一步的 API 来充当可写集合、数据库客户端还是长颈鹿,从 OOP 的角度来看都是完全不相关的。【参考方案2】:

可能有几个原因。以下是一些:

巨大的向后兼容性问题

ICollection&lt;T&gt; 的定义你会怎么写?这看起来很自然:

interface ICollection<T> : IReadOnlyCollection<T>

    int Count  get; 

但它有一个问题,因为IReadOnlyCollection&lt;T&gt; 还声明了一个Count 属性(编译器会在此处发出警告)。除了警告之外,保持原样(相当于编写new int Count)允许实现者通过显式实现至少一个Count 属性来对两个Count 属性进行不同 实现。如果两个实现决定返回不同的值,这可能是“有趣的”。允许人们朝自己的脚开枪并不是 C# 的风格。

好的,那怎么样:

interface ICollection<T> : IReadOnlyCollection<T>

    // Count is "inherited" from IReadOnlyCollection<T>

这会破坏所有决定显式实现Count 的现有代码:

class UnluckyClass : ICollection<Foo>

     int ICollection<Foo>.Count  ...  // compiler error!

因此,在我看来,这个问题没有好的解决方案:要么破坏现有代码,要么对每个人强制执行容易出错的实现。所以the only winning move is not to play。

【讨论】:

不确定我是否理解您的第二个原因。我认为您提供的代码示例是可能的、有效的和可编译的。 “选择加入”是什么意思? @ZaidMasud:那里的写作需要一些改进。选择加入 = 您需要明确表示您是一个只读集合——与“每个人都实现这一点,因为 BCL 团队这么说”相反。 但在当前实现中,布尔条件 (new List&lt;string&gt;() is IReadOnlyCollection&lt;Foo&gt;) 的计算结果为 true @Jon,也许你混淆了只读和不可变? IReadOnlyCollection&lt;T&gt; 的意义在于让你编写一个声明“我只需要读取这个集合,我保证不会添加或删除任何项目”的方法(忽略运行时强制转换的可能性,ew)。对于编写不关心项目集合是否可变的通用代码非常有用。再举一个例子,一个由进程以只读方式打开的文件具有在进程背后更改的能力。然而,这个进程不会以追加模式打开文件,因为它只想执行读取操作。 @Jon,第 2 点由于多种原因无效。最重要的是,它歪曲了 IReadOnly* 接口的目的——它们呈现集合的只读 视图,指示集合在特定上下文中的使用方式。这并不表示该系列不能或不会改变。其次,ICollection.IsReadOnly 属性可以满足您所追求的目的。运行时类型检查是错误的作业机制。【参考方案3】:

这将是语义上的错误,因为显然,并非每个ICollection 都是只读的。

也就是说,他们可以调用接口IReadableCollection,而实现可以调用ReadOnlyCollection

但是,他们没有走那条路。为什么?我看到了BCL team member write that they didn't want the collections API to become too convoluted。 (虽然它已经是,坦率地说。)

【讨论】:

正如 Zaid 的 comment 在我的回答中强调的那样,语义正确性不可能是这里的决定性原因——否则他们不会让 List&lt;T&gt; 实现 IReadOnlyCollection&lt;T&gt; -1 这种推理的问题在于它意味着List&lt;T&gt;的实现是语义上错误的,因为它实现了IReadOnlyCollection&lt;T&gt;,显然,List&lt;T&gt;不是只读的。 理想情况下,ICollection&lt;T&gt; 应该从 IReadableCollection&lt;out T&gt;IWritableCollection&lt;in T&gt; 继承,如果通过这些接口“查看”,则在每个集合上启用协变和逆变。 又一个将只读与不可变混淆的答案。正如@binki 在另一个答案的评论中所说,“通用 IReadOnlyCollection 的全部意义在于您可以将不可变列表和可变列表都视为只读列表。”我不在乎我是否可以更改列表,我只关心我是否可以读取列表无论是否也支持修改权限。 投反对票。很遗憾看到一个明显错误的答案吸引了这么多的赞成票。你只需要知道 List 实现了 IReadOnlyList 就可以看出这个答案中的逻辑是错误的。这些接口的命名可能不好,但它们在 BCL 中的使用非常清楚地表明了它们的含义 - IReadOnlyCollection 表示一个集合的只读视图,该集合本身可能是可变的。【参考方案4】:

这个界面更多的是描述界面,而不是真正的功能性东西。

在建议的方法中,每个集合都必须被理解为你只能阅读的东西,广告总是一样的。因此,您无法在创建后添加或删除事物。

我们可以问自己为什么接口被称为IReadOnlyCollection,而不是'IReadOnlyEnumerable',因为它使用IEnumerable&lt;T&gt;, IEnumerable。答案很简单,这种类型的接口不会有任何意义,因为 Enumerable 已经是“只读”了。

让我们看看文档IReadOnlyCollection(T)

描述中的第一个(也是最后一个)senetece 说:

表示强类型的只读元素集合。

如果您知道strong-typing 的用途,我认为这可以解释一切。

【讨论】:

-1 我发现很难理解你的论点。我无法理解强类型和这个讨论之间的任何关系。这两种情况都是完全强类型的。其实the entire C# language is strongly typed. 强类型概念,简单的单词可以表示为。如果 A 是某种东西,而 B 是其他东西。 A 不能是 B。因此,如果某物是 Collection,则不能是 ReadOnlyCollection。因此,这有一些逻辑。 那么如果A=List(是什么)和B=IEnumerable(是别的),那么List就不能是IEnumerable吗?请不要试图用毫无意义的东西制造逻辑。 不,那么我说不能列出枚举。如果你认为你可以写在只读的东西上,这是你的法律。哪怕一点也不理智。即使我的逻辑对你来说很奇怪,它也符合设计师的想法。你不处理它。 EOT。 在这一点上我不得不和 Zaid 一起去,面向对象编程的重点,以及像 DRY 这样的概念是代码的可重用性。如果 IGiraffe 不能像 IMammal 一样对待,而 IMammal 不能像 IAnimal 一样对待,那么拥有基类或基接口真的没有意义。【参考方案5】:

面向对象的设计推理源于类与该类实现的任何接口之间的“Is A”关系。

在 .NET 4.5/c# 5.0 中,List&lt;T&gt; 既是 ICollection&lt;T&gt; 又是 IReadOnlyCollection&lt;T&gt;。您可以将 List&lt;T&gt; 实例化为任一类型,具体取决于您希望如何使用您的集合。

虽然让ICollection&lt;T&gt; 实现IReadOnlyCollection&lt;T&gt; 将使您能够让List&lt;T&gt; 成为ICollection&lt;T&gt;IReadOnlyCollection&lt;T&gt;,但这样做会说ICollection&lt;T&gt; “是A” IReadOnlyCollection&lt;T&gt; 不是。

更新

如果从ICollection&lt;T&gt; 继承的每个类都实现了IReadOnlyCollection&lt;T&gt;,那么我会说让ICollection&lt;T&gt; 实现接口更有意义。既然不是这样,想想如果ICollection&lt;T&gt; 实现IReadOnlyCollection&lt;T&gt; 会是什么样子:

public interface IReadOnlyCollection<T> : IEnumerable<T>, IEnumerable public interface ICollection<T> : IReadOnlyCollection<T>

如果您查看ICollection&lt;T&gt; 的签名,它看起来像IS-A 只读集合。等等,让我们看看 IReadOnlyCollection 和啊……它实现了IEnumerable&lt;T&gt;IEnumerable,所以它不一定是只读集合。在功能上,原始问题提出的实现是相同的,但在语义上,我认为将接口实现推到尽可能高的位置更正确。

【讨论】:

您不能将List&lt;T&gt; 实现为任一类型;它是框架提供的一个类,已经为你实现了。 当然可以。一个简单的不太有用的例子是IReadOnlyCollection&lt;T&gt; collection = new List&lt;T&gt;();。如果您正在设计一个只想返回只读集合的​​框架,您可能有一个属性或方法返回一个 IReadOnlyCollection 而不是 ListICollectionIEnumerable 等。 好的,所以你的意思是“声明”或“实例化”而不是“实施”。我看到你的编辑。但是如果你同意List&lt;T&gt; IS-A IReadOnlyCollection&lt;T&gt;,那么同样的道理,ICollection&lt;T&gt; IS-A IReadOnlyCollection&lt;T&gt; (我编辑了我的答案以使用正确的术语)。我不遵循你的逻辑。 List&lt;T&gt; IS-A IReadOnlyCollection&lt;T&gt; 因为List&lt;T&gt; 实现了IReadOnlyCollection&lt;T&gt;ICollection&lt;T&gt; 没有实现 IReadOnlyCollection&lt;T&gt;,因此 ICollection&lt;T&gt; IS-NOT-A IReadOnlyCollection&lt;T&gt;(不应该)。 稍微考虑一下...根据您的最后一个推理,如果ICollection&lt;T&gt; 确实 实现了IReadOnlyCollection&lt;T&gt;,那么它将是IReadOnlyCollection&lt;T&gt;?为什么 List 应该是只读的而 ICollection 不是?什么是关于 List 的只读而不是关于 ICollection 的情况?【参考方案6】:

Jon 就在这里 https://***.com/a/12622784/395144 ,您应该将他的回复标记为答案:

int ICollection<Foo>.Count  ...  // compiler error!

由于接口可以有显式实现,提取基接口不向后兼容(使用基类就没有这个问题)。

这就是为什么...

Collection<T> : IReadOnlyCollection<T>
List<T> : IReadOnlyList<T>
Dictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>

...但不是它们的接口。

恕我直言,他们最初犯了一个设计错误,现在完全无法解决(不破坏任何东西)。

编辑:隐藏没有帮助,旧的(显式)实现仍不会构建(不修改代码):

interface INew<out T>  T Get(); 

interface IOld<T> : INew<T>

    void Set(T value);
    new T Get();


class Old<T> : IOld<T>

    T IOld<T>.Get()  return default(T); 
    void IOld<T>.Set(T value)  

'Sample.Old' 没有实现接口成员'Sample.INew.Get()'

【讨论】:

有趣的一点,但是您忘记了ICollectionIReadOnlyCollection 都可以定义Count(派生类将使用new 关键字隐藏基类成员)。您可以通过Eric Lippert 在this answer 中看到发生这种情况的示例。所以,虽然是一个很好的观点,但你的回答本身并不足以成为选择他们所做设计的充分理由。 @ZaidMasud 对于隐藏,我已经在编辑中回答(cmets 太短):) 出于设计原因......正如我所写,我认为这是一个(初始)错误Microsoft(如果 ICollection.IsReadOnly 为 true,则只读的、编译时安全的子接口比引发 NotSupportedException 的 Add 方法好得多)。 你是对的。太糟糕了,你错过了赏金期 :) 尽管 Jon 陈述了类似的原因,但他从“允许实现者对两个 Count 有不同的实现”的角度来看待它,而不是表明它会导致破坏性变化,使其成为非向后兼容。 另见infoq.com/news/2011/10/ReadOnly-WInRT ...似乎MS“捍卫”了它的选择(反对.NET社区的要求),直到他们被WinRT强迫......以及官方的MS观点: msdn.microsoft.com/en-us/magazine/jj133817.aspx :“我们的立场是,这个特定方面(只读)最好使用可选特征模式建模 [...] 多年来,我们得出的结论是,添加只读集合接口是值得增加的复杂性” @Zaid:好吧,恕我直言,乔恩用它的“UnluckyClass”回答;)总是在主题:请参阅实现 IList 而不是 ICollection 的“旧” ReadOnlyCollection 包装类...除了名称之外,它还强制您使用列表而不是 collection,并编写像这样 (*) 的错误代码:ICollection c = ...; var ro = new ReadOnlyCollection(c as IList ?? c.ToArray()) 所以... MS 也会出错 :)

以上是关于为啥泛型 ICollection 不在 .NET 4.5 中实现 IReadOnlyCollection?的主要内容,如果未能解决你的问题,请参考以下文章

泛型接口(协变和逆变)

为啥 HashSet<T> 没有实现 ICollection?

C#中List(Of T)类

为啥不在 VB.NET 的 web 服务的参数中暴露 List(Of String)? [复制]

为啥 Asp.Net MVC 不在我的共享目录中搜索视图?

为啥 VsPerfAspNetCmd 会抛出错误“值不在预期范围内”?