开发者

Why do core types implement interfaces only partly?

开发者 https://www.devze.com 2023-03-07 12:11 出处:网络
Q1 Why do new classes from .NET implement interfaces only partially? Q2 Shall I do the same in my code?

Q1 Why do new classes from .NET implement interfaces only partially?

Q2 Shall I do the same in my code?

I asked this question here, so I thought, okay that was long time ago, you can have different usage et开发者_C百科c etc, and now such implementation is supported only for consistency reasons. But new classes also do that.

Old

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

New

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

UPDATE

I am not worried why I get exceptions, it is clear. But I don't understand why classes implement the well defined contract, but not all the members (which can lead to unpleasant run time exceptions)?


You could argue that the interfaces weren't granular enough in the original design. For example, most people never use SyncRoot - it could perhaps have been on a different interface. Likewise, it is unfortunate that no interface offers read-only indexer access, for example.

As it stands, the interfaces are what they are. It is still very convenient to implement the main IList[<T>]/ICollection[<T>]/IEnumerable[<T>] interfaces though - it offers the majority of callers access to what they need... so indexers in the first example, and Add in the second.

To be fair, they do also offer IsFixedSize and IsReadOnly - querying the first would have led you to not call Add. Re SyncRoot - that presumably can't make sense inside ConcurrentQueue<T>, and any implementation would break the logic of the type. Normally I would say "then it isn't that type; don't implement the interface", but to repeat my earlier statement... most people never use SyncRoot - so I'm OK with it ;p


One minor point, all interfaces have to be fully implemented. All methods and properties of an interface must be implemented by any implementor – otherwise the compiler. You are referring to the runtime errors that can be thrown when you call some methods of an interface.

The documentation for IList states:

IList is a descendant of the ICollection interface and is the base interface of all non-generic lists. IList implementations fall into three categories: read-only, fixed-size, and variable-size. A read-only IList cannot be modified. A fixed-size IList does not allow the addition or removal of elements, but it allows the modification of existing elements. A variable-size IList allows the addition, removal, and modification of elements.

When you call a method that cannot be satisfied by a particular implementation then you get an exception.

Why was the interface designed this way? One can only speculate, but this particular design allows for the IsFixedSize, IsReadOnly etc. properties to change during the lifetime of an instance of the interface.

Should you design your interfaces this way? That depends on whether such a design is desirable and meets your needs.


From OOP standpoint this is simply wrong. They should either have made smaller interfaces or not put them on stuff like Arrays. However the .NET Framework designers are not stupid and they probably made some tradeoff. For example IEnumerable is required to implement IDisposable which does not make sense from OOP design standpoint but there are some performance benefits for database readers for example. So probably implementations that are hacked into a class (like your example) have some benefit but from OOP point of view they are wrong and you should not do it unless you are aware what you are trading the good design for.


I suspect the existence of partially-implemented interfaces is a consequence of a design decision not to allow a class or interface to have independent readable property and writable properties of the same name and automatically use them appropriately (e.g. if a class has a readable property 'foo' and a writable property 'foo', use the readable property for reads and the writable one for writes). This design decision made it awkward to split off the reading and writing aspects of certain interfaces.

Ideally, instead of having a single interface IList, there would have been generic contravariant interface IReadableByIndex, generic covariant interfaces IWriteableByIndex, IAppendable, IGrowableByIndex (includes various insert and delete functions), and non-generic IMovableByIndex (index-based copy, swap, and roll functions) and maybe IComparableByIndex (given two indices, compare the items). An interface IList could implement all of those, but there would be many useful subsets as well (many of which could be contravariant or covariant). Note that the existence of some non-generic routines would allow things like sorts to be implemented on any collection that implements IComparableByIndex and IMovableByIndex without having to worry about the exact type of the collection.

Unfortunately, for a split of IList to have been really useful, it would have been necessary to have IReadableByIndex and IWritableByIndex as separate interfaces. This in turn would have posed difficulties when trying to write code that would inherit both interfaces, as the compiler would complain about ambiguity when trying to use the indexed accessor. Since the IReadableByIndex and IWritableByIndex ended up having to be combined, Microsoft probably figured it may as well lump everything into IList.

0

精彩评论

暂无评论...
验证码 换一张
取 消