21

看看ReadOnlyCollection类的规范,它确实实现了IList接口,对。

IList 接口有 Add/Update/Read 方法,我们称之为接口的前置条件。如果我有 IList,我应该能够在我的应用程序中的任何地方执行所有此类操作。

但是,如果我在代码中的某处返回 ReadOnlyCollection 并尝试调用 .Add(...) 方法呢?它抛出一个 NotSupportedException。你认为这是一个糟糕设计的好例子吗?另外,这个类是否违反了Liskov 替换原则

微软为何采用这种方式?让这个 ReadOnlyCollection 只实现 IEnumerable 接口(顺便说一句,它已经是只读的)是否应该更容易(更好)?

4

8 回答 8

9

是的,这确实是糟糕的设计。.NET 中缺少集合接口:没有只读接口。

您知道这些string[]工具IList<string>(其他类型的工具也是如此)吗?这有同样的问题:你会期望你可以在接口上调用AddRemove,但它会抛出。

不幸的是,如果不破坏向后兼容性,就不能再改变了,但我同意你的观点,这是非常糟糕的设计。更好的设计会看到只读功能的单独接口。

于 2010-10-08T23:27:28.883 回答
9

虽然IList<T>接口定义Add(T)Insert(int,T)方法,但它也定义了IsReadOnly属性,如果您仔细阅读MSDN 上IList.Insert(int,T)IList.Add(T)方法的定义,您可以看到它们都指定NotSupportedException如果 list 是方法可以抛出只读。

因为这个原因说它是糟糕的设计就像说它也是糟糕的设计,因为当索引为负数或大于集合大小时Insert(int, T)会抛出。ArgumentOutOfRangeException

于 2010-10-08T23:37:05.520 回答
5

在我看来,这不是一个伟大的设计,而是一个必要的邪恶。

不幸的是,微软没有在框架中包含类似IReadableList<>and的东西,并且自己实现了这两者(甚至完全跳过并实现了)。问题解决了。IWriteableList<>IList<>IList<>IWriteableList<>IReadableList<>

但是现在改变为时已晚,如果您遇到需要集合具有列表语义的情况,并且您希望在运行时抛出异常而不是允许突变,那么ReadOnlyCollection<>不幸的是,这是您的最佳选择。

于 2010-10-08T23:30:55.487 回答
2

IList 有一些读取方法和属性,例如 Item 和 IndexOf(..)。如果 ReadOnlyCollection 只实现 IEnumerable 那么你就会错过这些。


有什么替代方案?拥有 IList 的只读版本和写入版本?这会使整个 BCL 复杂化(更不用说 LINQ)。


此外,我不认为它违反了 Liskov 替换原则,因为它是在(IList 的)基础级别定义的,它可以抛出不受支持的异常。

于 2010-10-08T23:22:39.033 回答
2

我认为这是抽象和专业化之间权衡的一个很好的例子。

你想要 IList 的灵活性,但你也想施加一些约束,那你怎么办?它的设计方式有点尴尬,并且可能在技术上违反了一些设计原则,但我不确定什么会更好,并且仍然为您提供相同的功能和简单性。

在这种情况下,最好有一个单独的 IListReadOnly 接口。但是,一旦使用界面扩散土地,很容易走上疯狂的道路,让事情变得非常混乱。

于 2010-10-08T23:27:45.670 回答
1

我会说这是一个糟糕的设计。即使人们接受具有能力查询的中等大接口的概念,也应该有其他接口继承自它们,以保证允许某些行为。例如:

  1. IEnumerable(很像现有的,但没有重置,并且不承诺如果在枚举期间更改集合会发生什么)
  2. IMul​​tipassEnumerable(添加重置,并保证不断变化的集合的重复枚举将返回相同的数据或抛出异常)
  3. ICountableEnumerable(一个多遍枚举,加上一个“计数”属性,以及一个同时获取枚举数和计数的方法)
  4. IModifiableEnumerable(一个 IEnumerator,如果在枚举期间由执行枚举的线程修改集合,则不会抛出。不会指定精确的行为,但在枚举期间未更改的项目必须仅返回一次;在枚举期间修改的项目必须返回每次添加或修改最多返回一次,如果它们在枚举开始时存在,则加一个。此接口本身不提供任何突变,但将与其他提供突变的接口一起使用)。
  5. ICopyableAsEnumerable(包括一个 count 属性和一个返回 IEnumerable 的方法,该方法表示列表的快照;实际上不是 IEnumerable 本身,而是 IEnumerable 提供的有用功能)。
  6. IImmutable(无成员,但可继承以创建保证不可变接口)
  7. IimmutableEnumerable
  8. IImmutableCountableEnumerable
  9. IList(可以是可读的、读写的或不可变的)
  10. IImmutableList(没有新成员,但继承了 IImmutable)
  11. IWritableList(没有新成员,但保证可写)

这只是一个小样本,但应该传达设计理念。

于 2010-12-04T04:41:25.970 回答
1

其糟糕的设计IMO。可能是由于向后兼容性问题,缺少协方差和逆变等等等。现在幸运的是,他们在 .NET 4.5 中通过以下方式解决了这个问题:

IReadOnlyList<out T>
IReadOnlyCollection<out T>
IReadOnlyDictionary<TKey, TValue>

但是我缺少带有“bool Contains(T)”的“只读”界面。

于 2013-07-03T13:37:32.623 回答
-3

我认为如果有一个不好的设计,这是一种在不检查 ReadOnly 属性的情况下添加到 IList 的习惯。程序员忽略接口部分的习惯并不意味着接口很差。

事实是,我们程序员中很少有人会费心去阅读规范。老实说,有很多事情对我来说似乎比坐下来阅读整个规范文档更令人兴奋。(例如,看看一个人是否真的可以用牙签睁开眼睛。)此外,我有一个限制,我无论如何都不会记住所有事情。

话虽如此,一个人不应该在不查看属性和方法列表的情况下使用接口。您认为名为“ReadOnly”的布尔属性的用途是什么?也许是因为只能出于某种原因阅读该列表。如果您从您自己的代码之外的某个地方传递一个列表,您应该在尝试添加之前检查该列表不是只读的。

于 2010-10-09T22:06:47.310 回答