【问题标题】:Why do core types implement interfaces only partly?为什么核心类型只部分实现接口?
【发布时间】:2011-08-29 14:09:42
【问题描述】:

Q1为什么 .NET 的新类仅部分实现接口?

Q2我应该在我的代码中做同样的事情吗?

我问了这个问题here,所以我想,好吧,那是很久以前的事了,你可以有不同的用法等等,现在这种实现只是出于一致性的原因才被支持。但新课程也可以做到这一点。

int[] list = new int[] {};
IList iList = (IList)list;
ilist.Add(1); //exception here

ICollection c = new ConcurrentQueue<int>();
var root = c.SyncRoot; //exception here

更新

我并不担心为什么我会遇到异常,这很清楚。但我不明白为什么实现定义明确的合同,而不是所有成员(这会导致令人不快的运行时异常)?

【问题讨论】:

    标签: c# .net collections interface


    【解决方案1】:

    一个小问题,所有接口都必须完全实现。接口的所有方法和属性必须由任何实现者实现——否则编译器。您指的是调用接口的某些方法时可能引发的运行时错误。

    IList 的文档指出:

    IList 是 ICollection 接口的后代,是所有非泛型列表的基接口。 IList 实现分为三类:只读、固定大小和可变大小。无法修改只读 IList。固定大小的 IList 不允许添加或删除元素,但允许修改现有元素。可变大小的 IList 允许添加、删除和修改元素。

    当您调用特定实现无法满足的方法时,您会遇到异常。

    为什么要这样设计界面?只能推测,但这种特殊设计允许IsFixedSizeIsReadOnly 等属性在接口实例的生命周期内发生变化。

    你应该这样设计你的界面吗?这取决于这样的设计是否可取并满足您的需求。

    【讨论】:

    • 这有点破坏了界面背后的想法。它应该定义一组通用的操作,你可以依赖于现有的任何实现接口的类型,这样你就可以在接口的不同实现之间切换,它们都会,嗯,工作我>。说“我们没有实现这个,所以有一个例外”会使接口变得毫无意义。
    • @jalf 假设你是反对者,我会要求你不要向信使开枪! ;-)
    • 别担心,我尽量不向任何信使开枪。 :) 虽然我会在最后一段中指出,你不仅仅是一个信使,因为你实际上对它是否(或可以)是好的设计持立场
    • @jalf 一旦你允许可变类型,那么我认为你接受这样的设计是可行的
    • @David,那么只实现那些需要的成员,而不强制类实现不完全支持的接口有什么问题?为什么我不能简单地拥有不是来自接口的公共方法?
    【解决方案2】:

    您可能会争辩说,原始设计中的界面不够精细。例如,大多数人从不使用 SyncRoot - 它可能在不同的界面上。同样,不幸的是,没有接口提供只读索引器访问。

    就目前而言,接口就是它们的本来面目。虽然实现主要的IList[&lt;T&gt;]/ICollection[&lt;T&gt;]/IEnumerable[&lt;T&gt;] 接口仍然非常方便 - 它为大多数调用者提供了访问他们需要的东西......所以索引器 在第一个示例中,Add 在第二个示例中。

    公平地说,他们也提供IsFixedSizeIsReadOnly - 查询第一个会导致您不致电Add。回复SyncRoot - 这在ConcurrentQueue&lt;T&gt; 中可能没有意义,任何实现都会破坏类型的逻辑。通常我会说“那么它不是那种类型;不要实现接口”,但重复我之前的陈述......大多数人从不使用SyncRoot - 所以我可以接受它;p

    【讨论】:

    • 大多数人从不使用 SyncRoot 不回答问题。我可以让类声明与接口中同名的公共方法,但我不会添加没有意义的属性/方法(例如我的示例中的AddSyncRoot
    • 缺少 IReadableByIndex 方法是恕我直言,.net 中的一个显着弱点,它大大削弱了逆变的有用性(即使 IList 不能,IReadableByIndex 也可能是逆变的)。我怀疑限制源于这样一个事实,即具有 ReadOnly 属性和 WriteOnly 属性的类不能被视为具有 ReadWrite 属性,尽管我不知道编译器不能简单地读取使用 ReadOnly 的任何原因属性和写入使用 WriteOnly 属性;我认为这会让很多事情变得更方便。
    【解决方案3】:

    从 OOP 的角度来看,这是完全错误的。他们应该要么制作更小的接口,要么不把它们放在像数组这样的东西上。然而 .NET Framework 的设计者并不愚蠢,他们可能做出了一些权衡。例如,IEnumerable 是实现 IDisposable 所必需的,从 OOP 设计的角度来看,这没有意义,但是对于数据库读取器来说,这有一些性能优势。所以可能被入侵到一个类中的实现(比如你的例子)有一些好处,但从 OOP 的角度来看,它们是错误的,除非你知道你在用良好的设计换取什么,否则你不应该这样做。

    【讨论】:

    • IEnumerable 不需要来实现IDisposableIEnumerator 也不需要。 IEnumerator&lt;T&gt; 是,当您考虑像 finally 的“迭代器块”之类的事情时,它已经一次又一次地带来了红利。
    • 仍然没有改变这样一个事实,即作为 OOP 设计,要求有一个可能为空的 disposle 方法很糟糕。我并不是说添加它的决定是错误的。我只是说他们用 OOP 设计换取了其他好处。顺便说一句,Reset 方法显然很糟糕,MS 承认这是一个错误:)
    • 是的,完全同意你的看法;
    • @Stilgar:有必要做以下三件事之一:(1)接受任何不能创建可以安全放弃而没有后果的枚举器的类型都不能合法地实现IEnumerable; (2) 要求任何调用GetEnumerator()的代码调用Dispose检查它返回的实际对象是否实现IDisposable,如果是则调用Dispose; (3) 要求任何调用GetEnumerator 的代码在结果上调用Dispose(它能够做到)并要求枚举器实现IDisposable。我认为选项 #3 是最不邪恶的。
    【解决方案4】:

    我怀疑部分实现的接口的存在是设计决定的结果,不允许类或接口具有独立的同名可读属性和可写属性并自动适当地使用它们(例如,如果一个类具有可读属性“foo”和可写属性“foo”,使用可读属性进行读取,使用可写属性进行写入)。这种设计决策使得分离某些接口的读写方面变得很尴尬。

    理想情况下,不是只有一个接口 IList,而是通用逆变接口 IReadableByIndex、通用协变接口 IWriteableByIndex、IAppendable、IGrowableByIndex(包括各种插入和删除函数)和非通用 IMovableByIndex(基于索引的副本,交换和滚动函数),也许还有 IComparableByIndex(给定两个索引,比较项目)。接口 IList 可以实现所有这些,但也会有许多有用的子集(其中许多可以是逆变的或协变的)。请注意,一些非泛型例程的存在将允许在任何实现 IComparableByIndex 和 IMovableByIndex 的集合上实现排序等操作,而不必担心集合的确切类型。

    不幸的是,要使 IList 的拆分真正有用,必须将 IReadableByIndex 和 IWritableByIndex 作为单独的接口。这反过来又会在尝试编写继承这两个接口的代码时造成困难,因为编译器在尝试使用索引访问器时会抱怨歧义。由于 IReadableByIndex 和 IWritableByIndex 最终不得不合并,微软可能认为它也可以将所有内容都集中到 IList 中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-13
      • 2015-01-05
      • 2016-10-26
      相关资源
      最近更新 更多