162

See the definition of System.Array class

public abstract class Array : IList, ...

Theoretically, I should be able to write this bit and be happy

int[] list = new int[] {};
IList iList = (IList)list;

I also should be able to call any method from the iList

 ilist.Add(1); //exception here

My question is not why I get an exception, but rather why Array implements IList?

Matthew
  • 10,244
  • 5
  • 49
  • 104
oleksii
  • 35,458
  • 16
  • 93
  • 163
  • 27
    **Good** question. I never liked the idea of fat interfaces (that’s the technical term for this kind of design). – Konrad Rudolph May 11 '11 at 18:19
  • 1
    The real (better) question would be why it supports `IList`. `IList` is legacy. – H H May 11 '11 at 18:25
  • How do you think it breaks substitution? I think it doesn't. See Brians answer. – H H May 11 '11 at 18:26
  • 4
    @Henk, see: http://blogs.msdn.com/b/bclteam/archive/2004/11/19/267089.aspx – Anthony Pegram May 11 '11 at 18:27
  • 2
    Does anybody actually care about LSP? It seems quite academic to me. – Gabe May 11 '11 at 18:30
  • 15
    @Gabe, then you need to work with larger codebases. Implementing a behavior (inheriting from an interface) and then simply ignoring the things you don't like/can't support leads to smelly, obfuscated, casting and finally: buggy code. – Marius Oct 17 '12 at 08:35
  • @Marius: You have to put the complexity somewhere. Let's say you have a method that can sort a member of its class. With a single `IList<>` interface, you can check `IsReadOnly` to see if you can sort. If you have separate `IRWList<>` and `IReadList<>` interfaces, what type do you make the member of your class? You can't use `IRWList<>` because then it couldn't hold read-only objects, so you have to make it a `IReadList<>` and then cast to `IRWList` to do your sorting. See -- smelly, obfuscated casting. – Gabe Oct 18 '12 at 05:51
  • 4
    @Gabe its the collection which implies mutability not its contained entities. You can make your class member of a type that implements both IRWList<> and IReadList<>, use if as IRWList<> internally in your class and expose it as IReadList. Yes, you have to put complexity somewhere, but I just don't see how that applies to disregarding LSP as a very good design principle (did not know about the IsReadOnly property though which makes IList more complex from a consumers standpoint) – Marius Oct 19 '12 at 10:22

8 Answers8

108

Because an array allows fast access by index, and IList/IList<T> are the only collection interfaces that support this. So perhaps your real question is "Why is there no interface for constant collections with indexers?" And to that I have no answer.

There are no readonly interfaces for collections either. And I'm missing those even more than a constant sized with indexers interface.

IMO there should be several more (generic) collection interfaces depending on the features of a collection. And the names should have been different too, List for something with an indexer is really stupid IMO.

  • Just Enumeration IEnumerable<T>
  • Readonly but no indexer (.Count, .Contains,...)
  • Resizable but no indexer, i.e. set like (Add, Remove,...) current ICollection<T>
  • Readonly with indexer (indexer, indexof,...)
  • Constant size with indexer (indexer with a setter)
  • Variable size with indexer (Insert,...) current IList<T>

I think the current collection interfaces are bad design. But since they have properties telling you which methods are valid (and this is part of the contract of these methods), it doesn't break the substitution principle.

Pang
  • 9,564
  • 146
  • 81
  • 122
CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • 15
    thanks for the answer. But I rather leave the question as is. The reason is simple. Interface is a public contract. If one implements it, one must fully implement all the members, otherwise it breaks LSP and generally smells bad, is it not? – oleksii May 11 '11 at 18:32
  • @olek But there is no good alternative to `IList` atm since this is the only collection interface with an indexer. And all the code that needs an indexer thus uses `IList`, in particular many linq methods do(Skip,ElementAt,Reverse,...) – CodesInChaos May 11 '11 at 18:42
  • 18
    It does break LSP. If it didn't list.Add(item) should add item to the list regardless of the concrete type. Except for exceptionel cases. In the array implementation in throws an exception in a non-exceptionel case, which in it self is bad practice – Rune FS May 13 '11 at 15:58
  • @RuneFS it doesn't break LSP, you're just using it wrong. You should be checking IsFixedSize or IsReadOnly before attempting to mutate the object. – smelch Aug 11 '14 at 21:11
  • 2
    @smelch I'm sorry but you got LSP wrong then. An array does not implement `add` and thus can't be substituted for something that does when that ability is required. – Rune FS Aug 12 '14 at 06:55
  • 9
    I concede that it *technically* does not violate LSP *only because* the documentation states you should check the `IsFixedSize` and `IsReadOnly` properties, it definitely violates the *Tell, Don't Ask principle* and *Principle of least surprise*. Why implement an interface when you're just going to throw exceptions for 4 out of the 9 methods? – Matthew Sep 05 '14 at 19:07
  • 18
    Some time has passed since the original question. But now with .Net 4.5, there are additional interfaces [IReadOnlyList](https://msdn.microsoft.com/en-us/library/hh192385.aspx) and [IReadOnlyCollection](https://msdn.microsoft.com/en-us/library/hh881542.aspx). – Tobias Apr 08 '15 at 12:16
  • Very simply this was done to avoid breaking changes which people would have complained about a lot more vocally than this. – Jordan Jun 30 '15 at 14:35
  • it does not breaks LSP if you check IsReadonly or IsFixedSize but it violate Open/Close. check LSP here http://stackoverflow.com/questions/4428725/can-you-explain-liskov-substitution-principle-with-a-good-c-sharp-example – ni3.net Jun 23 '16 at 11:58
46

The remarks section of the documentation for IList says:

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.

Obviously, arrays fall into the fixed-size category, so by the definition of the interface it makes sense.

Pang
  • 9,564
  • 146
  • 81
  • 122
Brian Rasmussen
  • 114,645
  • 34
  • 221
  • 317
  • 5
    I guess they would have ended up with a lot of interfaces. IListFixedSize, IListReadOnly... – Magnus May 11 '11 at 18:26
  • 14
    that'a actually a good answer from the documentation's point of view. But to me it rather looks like a hack. Interfaces must be thin and simple in order for a class to implement all the members. – oleksii May 11 '11 at 18:38
  • 1
    @oleksii: I agree. Interfaces and runtime exceptions are not the most elegant combination. In defense of `Array` it does implement the `Add` method explicitly, which reduces the risk of calling it by accident. – Brian Rasmussen May 11 '11 at 18:41
  • Until we create an implementation of `IList` that disallows both modification *and* addition/removal. Then the documentations is no longer correct. :P – Timo Oct 24 '16 at 15:42
  • 2
    @Magnus - In .Net 4.5, there are additional interfaces [IReadOnlyList](https://msdn.microsoft.com/en-us/library/hh192385.aspx?f=255&MSPPError=-2147217396) and [IReadOnlyCollection](https://msdn.microsoft.com/en-us/library/hh881542.aspx?f=255&MSPPError=-2147217396). – RBT Apr 24 '17 at 03:03
  • @RBT But afaik, unfortunately not the opposite to exclude collections that can't be written to. – jeromej Dec 17 '21 at 12:52
  • @BrianRasmussen unless you were trying to expose an interface to not force a concrete implementation. – jeromej Dec 17 '21 at 12:52
19

Because not all ILists are mutable (see IList.IsFixedSize and IList.IsReadOnly), and arrays certainly behave like fixed-size lists.

If your question is really "why does it implement a non-generic interface", then the answer is that these were around before generics came along.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • 10
    @oleksii: No, it doesn't break LSP, because the interface `IList` *itself* tells you that it may not be mutable. If it was in fact guaranteed to be mutable and the array told you otherwise, *then* it would break the rule. – user541686 May 11 '11 at 18:34
  • 2
    Actually, Array does break LSP in case of generic `IList` and doesn't break it in case of non-generic `IList`: http://enterprisecraftsmanship.com/2014/11/22/read-only-collections-and-lsp/ – Vladimir Nov 22 '14 at 15:04
6

It's a legacy that we have from the times when it wasn't clear how to deal with read only collections and whether or not Array is read only. There are IsFixedSize and IsReadOnly flags in the IList interface. IsReadOnly flag means that collection can't be changed at all and IsFixedSize means that collection does allow modification, but not adding or removal of items.

At the time of .Net 4.5 it was clear that some "intermediate" interfaces are required to work with read only collections, so IReadOnlyCollection<T> and IReadOnlyList<T> were introduced.

Here is a great blog post describing the details: Read only collections in .NET

David Klempfner
  • 8,700
  • 20
  • 73
  • 153
Vladimir
  • 1,630
  • 2
  • 18
  • 28
  • An array is not read only you can simply set values at indexes. An array just has a fixed size but there isn't an `IFixedSizeList` . – Wouter Dec 08 '22 at 22:17
0

Definition of IList interface is "Represents a non-generic collection of objects that can be individually accessed by index.". Array completely satisfies this definition, so must implement the interface. Exception when calling Add() method is "System.NotSupportedException: Collection was of a fixed size" and occurred because array can not increase its capacity dynamically. Its capacity is defined during creation of array object.

meir
  • 916
  • 8
  • 13
0

Having an array implement IList (and transitively, ICollection) simplified the Linq2Objects engine, since casting the IEnumerable to IList/ICollection would also work for arrays.

For example, a Count() ends up calling the Array.Length under-the-hood, since it's casted to ICollection and the array's implementation returns Length.

Without this, the Linq2Objects engine would not have special treatment for arrays and perform horribly, or they'd need to double the code adding special-case treatment for arrays (like they do for IList). They must've opted to make array implement IList instead.

That's my take on "Why".

Herman Schoenfeld
  • 8,464
  • 4
  • 38
  • 49
0

Also implementation details LINQ Last checks for IList , if it did not implement list they would need either 2 checks slowing down all Last calls or have Last on an Array taking O(N)

user1496062
  • 1,309
  • 1
  • 7
  • 22
0

An Array is just one of many possible implementations of IList.

As code should be loosely coupled, depend on abstractions and what not... The concrete implementation of IList that uses consecutive memory (an array) to store it's values is called Array. We do not "add" IList to the Array class that's just the wrong order of reasoning; Array implements IList as an array.

The exception is exactly what the interface defines. It is not a surprise if you know the whole interface not just a single method. The interface also give you the opportunity to check the IsFixedSize property and see if it is safe to call the Add method.

Wouter
  • 2,540
  • 19
  • 31