Yes, it is bad design indeed. The collection interfaces are lacking in .NET: there are no read-only interfaces.
Did you know that string[] implements IList<string> (and ditto for other types)? This has the same problem: you would expect that you can call Add and Remove on the interface, but it would throw.
Unfortunately, this cannot be changed anymore without breaking backwards compatibility, but I agree with you that it is very bad design. A better design would have seen separate interfaces for the read-only capabilities.
Although IList<T> interface defines Add(T) and Insert(int,T) methods, it also defines IsReadOnly property and if you read carefully definition of IList.Insert(int,T) and IList.Add(T) methods on MSDN, you can see that they both specify that methods could throw NotSupportedException if list is read-only.
Saying that it's bad design for that reason is like saying that it is also bad design because Insert(int, T) can throw ArgumentOutOfRangeException when index is negative or bigger than the size of collection.
It's not a great design, but a necessary evil in my opinion.
It's unfortunate that Microsoft didn't include something like IReadableList<> and IWriteableList<> in the framework and have IList<> itself implement both of those (or even skip IList<> altogether and have IWriteableList<> implement IReadableList<>). Problem solved.
But it's too late to change now, and if you have a situation where you need your collection to have list semantics and you'd prefer to throw an exception at runtime rather than allow mutations, then ReadOnlyCollection<> is, unfortunately, your best option.
IList has some read method and properties like Item, and IndexOf(..). If ReadOnlyCollection would implement IEnumerable only then you would miss out on those.
Whats the alternative? Having a readonly version of IList and a write version? That would complicate the entire BCL (not to talk about LINQ).
Also I don't think it violates the Liskov Substitution Principle because it is defined at the base level (of IList) that it can throw a not supported exception.
I think it's a good example of a trade off between abstraction and specialization.
You want the flexibility of IList, but you also want to impose some constraints, so what do you do? The way it's designed is a little awkward, and probably technically violates some design principles, but I'm not sure what would be better and still give you the same functionality and simplicity.
In this case it may have been better to have a separate IListReadOnly interface. However, it is easy to go down the path to crazy one time use interface proliferation land and make things very confusing.
I would say it's a bad design. Even if one accepts the concept of a moderately large interface with capability queries, there should have been other interfaces which inherited from them which would guarantee that certain behaviors be allowed. For example:
IEnumerable (much like existing one, but without reset, and no promise of what will happen if the collection is changed during enumeration)
IMultipassEnumerable (adds reset, and guarantees repeated enumerations of a changing collection will either return the same data or throw and exception)
ICountableEnumerable (a multipass enumerable, plus a 'count' property, and a method to get an enumerator and count simultaneously)
IModifiableEnumerable (an IEnumerator which will not throw if a collection is modified during enumeration by the thread doing the enumerating. The precise behavior would not be specified, but items which are unchanged during enumeration must be returned exactly once; those which are modified during enumeration must be returned at most once for each addition or modification, plus one if they existed at enumeration start. This interface itself does not provide any mutations, but would be used in conjunction with others that do).
ICopyableAsEnumerable (includes a count property, and a method to return an IEnumerable which represents a snapshot of the list; not actually an IEnumerable itself, but a useful feature for an IEnumerable to provide).
IImmutable (no members, but inheritable to create guaranteed-immutable interfaces)
IImmutableEnumerable
IImmutableCountableEnumerable
IList (could be readable, read-write, or immutable)
IImmutableList (no new members, but inherits IImmutable)
IWritableList (no new members, but is guaranteed writable)
That's just a small sampling, but should convey the design idea.
Its bad design IMO. Probably forced by backward compatibility issues, missing co-variance and contra-variance etc. etc. Now luckily they addressed it in .NET 4.5 with the:
I think if there is a bad design going on it is a habit of adding to an IList without checking the ReadOnly property. The habit of programmers to ignore portions of an interface doesn't mean the interface is poor.
The truth is that few of us programmers ever bother to read the specifications. And truthfully there are many things that seem more exciting to me than sitting down and reading through the entire specification document. (Things like seeing if one really can hold the eyes open with toothpicks for example.) Besides, I have the limitation that I wouldn't remember everything anyway.
Having said that, one should not use an interface without at least looking at the list of properties and methods. And just what purpose do you think a boolean property named "ReadOnly" is for? Perhaps because the list can be read only for one reason or another. And if you are taking a list passed from someplace outside your own code you should check that the list is not read only before you try to add to it.
发布评论
评论(8)
是的,这确实是糟糕的设计。 .NET 中缺少集合接口:没有只读接口。
您是否知道
string[]
实现了IList
(其他类型也是如此)?这也有同样的问题:您希望可以在界面上调用Add
和Remove
,但它会抛出异常。不幸的是,在不破坏向后兼容性的情况下不能再改变这一点,但我同意你的观点,这是非常糟糕的设计。更好的设计应该为只读功能提供单独的接口。
Yes, it is bad design indeed. The collection interfaces are lacking in .NET: there are no read-only interfaces.
Did you know that
string[]
implementsIList<string>
(and ditto for other types)? This has the same problem: you would expect that you can callAdd
andRemove
on the interface, but it would throw.Unfortunately, this cannot be changed anymore without breaking backwards compatibility, but I agree with you that it is very bad design. A better design would have seen separate interfaces for the read-only capabilities.
虽然
IList
接口定义了Add(T)
和Insert(int,T)
方法,但它还定义了IsReadOnly
code> 属性,并且如果您仔细阅读 IList 的定义。插入(int,T)和IList MSDN 上的 .Add(T) 方法,您可以看到它们都指定如果 list 是只读的,方法可能会抛出NotSupportedException
。因为这个原因说它是糟糕的设计就像说它也是糟糕的设计,因为当索引为负或大于大小时
Insert(int, T)
可能会抛出ArgumentOutOfRangeException
的收藏。Although
IList<T>
interface definesAdd(T)
andInsert(int,T)
methods, it also definesIsReadOnly
property and if you read carefully definition of IList.Insert(int,T) and IList.Add(T) methods on MSDN, you can see that they both specify that methods could throwNotSupportedException
if list is read-only.Saying that it's bad design for that reason is like saying that it is also bad design because
Insert(int, T)
can throwArgumentOutOfRangeException
when index is negative or bigger than the size of collection.这不是一个伟大的设计,但在我看来是一个必要的罪恶。
不幸的是,微软没有在框架中包含诸如
IReadableList<>
和IWriteableList<>
之类的东西,并且拥有IList<>
本身实现这两个(或者甚至完全跳过IList<>
并让IWriteableList<>
实现IReadableList<>
)。问题解决了。但现在改变已经太晚了,如果您需要集合具有列表语义,并且您更愿意在运行时抛出异常而不是允许突变,那么
ReadOnlyCollection<> 是你最好的选择。
It's not a great design, but a necessary evil in my opinion.
It's unfortunate that Microsoft didn't include something like
IReadableList<>
andIWriteableList<>
in the framework and haveIList<>
itself implement both of those (or even skipIList<>
altogether and haveIWriteableList<>
implementIReadableList<>
). Problem solved.But it's too late to change now, and if you have a situation where you need your collection to have list semantics and you'd prefer to throw an exception at runtime rather than allow mutations, then
ReadOnlyCollection<>
is, unfortunately, your best option.IList 有一些读取方法和属性,如 Item 和 IndexOf(..)。如果 ReadOnlyCollection 仅实现 IEnumerable 那么您就会错过这些。
还有什么替代方案吗?有 IList 的只读版本和写入版本吗?这会使整个 BCL 变得复杂(更不用说 LINQ)。
另外,我不认为它违反了里氏替换原则,因为它是在(IList)基础级别定义的,它可以抛出不支持的异常。
IList has some read method and properties like Item, and IndexOf(..). If ReadOnlyCollection would implement IEnumerable only then you would miss out on those.
Whats the alternative? Having a readonly version of IList and a write version? That would complicate the entire BCL (not to talk about LINQ).
Also I don't think it violates the Liskov Substitution Principle because it is defined at the base level (of IList) that it can throw a not supported exception.
我认为这是抽象和专业化之间权衡的一个很好的例子。
你想要IList的灵活性,但你也想施加一些约束,那么你该怎么办?它的设计方式有点尴尬,并且可能在技术上违反了一些设计原则,但我不确定什么会更好,并且仍然为您提供相同的功能和简单性。
在这种情况下,最好有一个单独的 IListReadOnly 接口。然而,很容易走上疯狂的一次性使用界面扩散之路,并使事情变得非常混乱。
I think it's a good example of a trade off between abstraction and specialization.
You want the flexibility of IList, but you also want to impose some constraints, so what do you do? The way it's designed is a little awkward, and probably technically violates some design principles, but I'm not sure what would be better and still give you the same functionality and simplicity.
In this case it may have been better to have a separate IListReadOnly interface. However, it is easy to go down the path to crazy one time use interface proliferation land and make things very confusing.
我想说这是一个糟糕的设计。即使人们接受具有功能查询的中等大接口的概念,也应该有从它们继承的其他接口,以保证允许某些行为。例如:
这只是一个小样本,但应该传达设计理念。
I would say it's a bad design. Even if one accepts the concept of a moderately large interface with capability queries, there should have been other interfaces which inherited from them which would guarantee that certain behaviors be allowed. For example:
That's just a small sampling, but should convey the design idea.
在我看来,它的设计很糟糕。可能是由于向后兼容性问题,缺少协方差和逆变等。现在幸运的是,他们在 .NET 4.5 中解决了这个问题:
但是我缺少带有“bool Contains(T)”的“只读”接口。
Its bad design IMO. Probably forced by backward compatibility issues, missing co-variance and contra-variance etc. etc. Now luckily they addressed it in .NET 4.5 with the:
However I am missing "read-only" interface with "bool Contains(T)".
我认为如果有一个糟糕的设计,那就是在不检查 ReadOnly 属性的情况下添加到 IList 的习惯。程序员忽略接口部分的习惯并不意味着接口很差。
事实上,我们程序员中很少有人会费心去阅读规范。说实话,对我来说,有很多事情比坐下来阅读整个规范文档更令人兴奋。 (例如,看看一个人是否真的可以用牙签睁开眼睛。)此外,我还有一个限制,那就是我无论如何都不会记住所有事情。
话虽如此,在不至少查看属性和方法列表的情况下,不应使用接口。您认为名为“ReadOnly”的布尔属性的用途是什么?也许是因为该列表只能出于某种原因而被读取。如果您要获取从您自己的代码之外的某个地方传递的列表,您应该在尝试添加列表之前检查该列表是否是只读的。
I think if there is a bad design going on it is a habit of adding to an IList without checking the ReadOnly property. The habit of programmers to ignore portions of an interface doesn't mean the interface is poor.
The truth is that few of us programmers ever bother to read the specifications. And truthfully there are many things that seem more exciting to me than sitting down and reading through the entire specification document. (Things like seeing if one really can hold the eyes open with toothpicks for example.) Besides, I have the limitation that I wouldn't remember everything anyway.
Having said that, one should not use an interface without at least looking at the list of properties and methods. And just what purpose do you think a boolean property named "ReadOnly" is for? Perhaps because the list can be read only for one reason or another. And if you are taking a list passed from someplace outside your own code you should check that the list is not read only before you try to add to it.