在.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>
?答案 0 :(得分:35)
可能有几个原因。这里有两个顶部:
巨大的向后兼容性问题
您如何撰写ICollection<T>
的定义?这看起来很自然:
interface ICollection<T> : IReadOnlyCollection<T>
{
int Count { get; }
}
但它有一个问题,因为IReadOnlyCollection<T>
也声明了一个Count
属性(编译器会在这里发出警告)。除了警告之外,将其保留为原样(相当于编写new int 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。
语义运行时类型检查
编写像
这样的代码毫无意义if (collection is IReadOnlyCollection<Foo>)
(或等效与反思),除非IReadOnlyCollection<T>
是“选择加入”:如果ICollection<T>
是IReadOnlyCollection<Foo>
的超集,则几乎所有太阳下的集合类都会通过此测试,使其在实践中无用。
答案 1 :(得分:14)
Jon就在这里https://stackoverflow.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()'
答案 2 :(得分:3)
语义错误,因为很明显,并非每个ICollection
都是只读的。
也就是说,他们可以调用接口IReadableCollection
,而实现可以调用ReadOnlyCollection
。
然而,他们并没有走那条路。为什么?我看到了BCL team member write that they didn't want the collections API to become too convoluted。 (虽然它已经是,但坦率地说。)
答案 3 :(得分:-2)
当我第一次阅读你的问题时,我想“嘿,他是对的!这才最有意义。”但接口的意义在于描述合同 - 行为的描述。如果您的类实现了IReadOnlyCollection
,那么 应该是只读的。 Collection<T>
不是。因此,对于Collection<T>
实现隐含只读的接口可能会导致一些非常讨厌的问题。 IReadOnlyCollection
的消费者会对底层集合做出某些假设 - 比如迭代它是安全的,因为没有人可以修改它等等。如果它的结构如你所暗示的那样可能不是真的!
答案 4 :(得分:-3)
面向对象的设计推理将源于类与该类实现的任何接口之间的“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>
或IReadOnlyCollection<T>
,但这样做会{ {1}} “是A” 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>
如果你要查看IEnumerable<T>
的签名,它看起来像 IS-A 只读集合。等等,让我们看看IReadOnlyCollection和啊......它实现了IEnumerable
和{{1}}所以它不一定是只读集合。在功能上,原始问题提出的实现是相同的,但从语义上来说,我认为将接口实现推到尽可能高的位置更为正确。
答案 5 :(得分:-4)
这个界面更像是一个描述界面然后真正的功能性的东西。
在建议的方法中,每个集合必须被理解为您可以完全阅读广告的内容始终相同。因此,您无法在创建后添加或删除任何内容。
我们可以问自己为什么界面被称为IReadOnlyCollection
,而不是'IReadOnlyEnumerable',因为它使用IEnumerable<T>, IEnumerable
。答案很简单,这种类型的界面不会有任何senece,因为Enumerable已经是“只读”。
因此,请查看文档IReadOnlyCollection(T)
描述中的第一个(也是最后一个)senetece说:
表示强类型的只读元素集合。
如果您知道strong-typing的用途,我认为这可以解释所有内容。