为啥泛型 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<T>
也实现 IReadOnlyCollection<T>
接口是否有意义:
public interface ICollection<T> : IEnumerable<T>, IEnumerable, *IReadOnlyCollection<T>*
这意味着实现ICollection<T>
的类会自动实现IReadOnlyCollection<T>
。这对我来说听起来很合理。
ICollection<T>
抽象可以看作是IReadOnlyCollection<T>
抽象的扩展。请注意,例如 List<T>
实现了 ICollection<T>
和 IReadOnlyCollection<T>
。
但是它并不是这样设计的。
我在这里缺少什么?为什么会选择当前的实现?
更新
我正在寻找一个使用面向对象设计推理来解释原因的答案:
一个具体的类,例如List<T>
实现 IReadOnlyCollection<T>
和 ICollection<T>
比以下更好的设计:
ICollection<T>
直接实现IReadOnlyCollection<T>
另外请注意,这基本上与以下问题相同:
-
为什么
IList<T>
不实现IReadOnlyList<T>
?
为什么IDictionary<T>
不实现IReadOnlyDictionary<T>
?
【问题讨论】:
这将如何影响向后兼容性? @asawyer 就我所见,它没有……除非你能提供一个反例? @asawyer 他们特别决定做出重大改变,将只读相关接口添加到 .NET 的集合中。 (知道实际会破坏代码的情况是相当人为的,不太可能在生产代码中普遍存在。) @ZaidMasud 好吧,在技术层面上,几乎所有事情都是一个突破性的变化。例如,通过添加新接口可能会影响方法重载决议。 我仍然不确定哪一方的论点最好。在多次换边之后,现在我倾向于“ICollection<T>
不应该实现IReadOnlyCollection<T>
和List<T>
实现IReadOnlyList<T>
没有意义”。更大的问题是当类C
实现接口I
时,这是否意味着C
可以I
或C
应该是I
。在此处查看等效问题:why-does-listt-implement-ireadonlylistt-in-net-4-5。发人深省的问题顺便说一句,+1!
【参考方案1】:
当我第一次阅读您的问题时,我想“嘿,他是对的!那是最有意义的。”但是接口的重点是描述契约——行为的描述。如果你的类实现了IReadOnlyCollection
,它应该是只读的。 Collection<T>
不是。因此,Collection<T>
实现一个隐含只读的接口可能会导致一些非常讨厌的问题。 IReadOnlyCollection
的消费者将对底层集合做出某些假设——比如迭代它是安全的,因为没有人可以修改它等等。如果它的结构像你建议的那样,那可能不是真的!
【讨论】:
List<T>
等集合类已被修改为同时实现 IReadOnlyCollection 和 ICollection... 那么当前设计中 List 的消费者不会遇到与您所说的相同的问题吗?
说得很好,扎伊德。事实上,最后一行:visualstudiomagazine.com/articles/2012/08/07/… 暗示 BEHAVIOR 不是实现或使用接口的标准!
对于不承诺不可变的类型IReadableCollection<out T>
或承诺永远包含同一组T 的接口IImmutableCollection<T>
会有很大的用处。对于承诺对象是“只读”但不保证它封装不可变序列的接口,我想不出太多用处。 IReadOnlyCollection<T>
用于本应称为 IReadableCollection<T>
的功能。
普通IReadOnlyCollection<T>
的全部意义在于您可以将不可变列表和可变列表都视为只读列表。它允许您定义消费者只需要读取一个索引值的 lsit 与每种类型的列表一起使用,因为即使是可变列表也能够在您通过IReadOnly
接口访问它们时保持静态。为什么不希望能够将可变列表传递给一个函数,该函数以只读索引列表作为输入来执行一些有用的任务?
这个答案很不合理。任何实现 IReadOnlyCollection
的类都实现了足够的 API 以将其视为只读集合。如果通过IReadOnlyCollection
类型的引用访问,您将只能将其用作只读集合。无论是实现进一步的 API 来充当可写集合、数据库客户端还是长颈鹿,从 OOP 的角度来看都是完全不相关的。【参考方案2】:
可能有几个原因。以下是一些:
巨大的向后兼容性问题
ICollection<T>
的定义你会怎么写?这看起来很自然:
interface ICollection<T> : IReadOnlyCollection<T>
int Count get;
但它有一个问题,因为IReadOnlyCollection<T>
还声明了一个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<string>() is IReadOnlyCollection<Foo>)
的计算结果为 true
。
@Jon,也许你混淆了只读和不可变? IReadOnlyCollection<T>
的意义在于让你编写一个声明“我只需要读取这个集合,我保证不会添加或删除任何项目”的方法(忽略运行时强制转换的可能性,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<T>
实现 IReadOnlyCollection<T>
。
-1 这种推理的问题在于它意味着List<T>
的实现是语义上错误的,因为它实现了IReadOnlyCollection<T>
,显然,List<T>
不是只读的。
理想情况下,ICollection<T>
应该从 IReadableCollection<out T>
和 IWritableCollection<in T>
继承,如果通过这些接口“查看”,则在每个集合上启用协变和逆变。
又一个将只读与不可变混淆的答案。正如@binki 在另一个答案的评论中所说,“通用 IReadOnlyCollection这个界面更多的是描述界面,而不是真正的功能性东西。
在建议的方法中,每个集合都必须被理解为你只能阅读的东西,广告总是一样的。因此,您无法在创建后添加或删除事物。
我们可以问自己为什么接口被称为IReadOnlyCollection
,而不是'IReadOnlyEnumerable',因为它使用IEnumerable<T>, 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<T>
既是 ICollection<T>
又是 IReadOnlyCollection<T>
。您可以将 List<T>
实例化为任一类型,具体取决于您希望如何使用您的集合。
虽然让ICollection<T>
实现IReadOnlyCollection<T>
将使您能够让List<T>
成为ICollection<T>
或IReadOnlyCollection<T>
,但这样做会说ICollection<T>
“是A” IReadOnlyCollection<T>
不是。
更新
如果从ICollection<T>
继承的每个类都实现了IReadOnlyCollection<T>
,那么我会说让ICollection<T>
实现接口更有意义。既然不是这样,想想如果ICollection<T>
实现IReadOnlyCollection<T>
会是什么样子:
public interface IReadOnlyCollection<T> : IEnumerable<T>, IEnumerable
public interface ICollection<T> : IReadOnlyCollection<T>
如果您查看ICollection<T>
的签名,它看起来像IS-A 只读集合。等等,让我们看看 IReadOnlyCollection 和啊……它实现了IEnumerable<T>
和IEnumerable
,所以它不一定是只读集合。在功能上,原始问题提出的实现是相同的,但在语义上,我认为将接口实现推到尽可能高的位置更正确。
【讨论】:
您不能将List<T>
实现为任一类型;它是框架提供的一个类,已经为你实现了。
当然可以。一个简单的不太有用的例子是IReadOnlyCollection<T> collection = new List<T>();
。如果您正在设计一个只想返回只读集合的框架,您可能有一个属性或方法返回一个 IReadOnlyCollection
而不是 List
、ICollection
、IEnumerable
等。
好的,所以你的意思是“声明”或“实例化”而不是“实施”。我看到你的编辑。但是如果你同意List<T>
IS-A IReadOnlyCollection<T>
,那么同样的道理,ICollection<T>
IS-A IReadOnlyCollection<T>
(我编辑了我的答案以使用正确的术语)。我不遵循你的逻辑。 List<T>
IS-A IReadOnlyCollection<T>
因为List<T>
实现了IReadOnlyCollection<T>
。 ICollection<T>
没有实现 IReadOnlyCollection<T>
,因此 ICollection<T>
IS-NOT-A IReadOnlyCollection<T>
(不应该)。
稍微考虑一下...根据您的最后一个推理,如果ICollection<T>
确实 实现了IReadOnlyCollection<T>
,那么它将是IReadOnlyCollection<T>
?为什么 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()'
【讨论】:
有趣的一点,但是您忘记了ICollection
和IReadOnlyCollection
都可以定义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 不在 .NET 4.5 中实现 IReadOnlyCollection?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 HashSet<T> 没有实现 ICollection?