Update 2011-Jan-06:
Believe it or not, I went ahead and included this interface in the open source library I started, Tao.NET . I wrote a blog post explaining this interface of the IArray<T> library, which not only solves the problems that I originally raised in this question (a year ago ?!), but also provides a covariant indexed interface , which is missing (in my opinion) in BCL.
Question (in short):
I asked why .NET has an IList<T> that implements ICollection<T> and therefore provides methods for modifying the list ( Add , Remove , etc.), but does not offer any intermediate interface such as IArray<T> to provide random access by index without changing the list.
EDIT 2010-Jan-21 2:22 pm EST:
In a commentary on John Skeet's first answer (in which he asked how often someone would have a need for a contract, such as IArray<T> ), I mentioned that the Keys and Values SortedList<TKey, TValues> class IList<TKey> and IList<Value> , respectively, to which John replied:
But in this case, he said that IList, and you know that you just use indexers. ,, It's not very elegant, I agree - but it doesn’t really hurt me.
This is reasonable, but I would answer by saying that it does not hurt you, because you simply know that you cannot do it . But the reason you know is not , which is cleared of code; this means that you have experience with the SortedList<TKey, TValue> .
Visual Studio will not give me any warnings if I do this:
SortedList<string, int> mySortedList = new SortedList<string, int>();
This is legal, according to IList<string> . But we all know that this will throw an exception.
Guillaume also made a suitable point:
Well, the interfaces are not perfect but the developer can check the IsReadOnly property before calling Add / Remove / Set ...
Again, this is reasonable, BUT : does this not mean that you will not beat cool?
Suppose I defined an interface as follows:
public interface ICanWalkAndRun { bool IsCapableOfRunning { get; } void Walk(); void Run(); }
Now suppose I used the usual practice to implement this interface, but only for my Walk method; in many cases, I would decide to set IsCapableOfRunning to false and throw a NotSupportedException on Run ...
Then I can have code that looks like this:
var walkerRunners = new Dictionary<string, ICanWalkAndRun>(); // ... ICanWalkAndRun walkerRunner = walkerRunners["somekey"]; if (walkerRunner.IsCapableOfRunning) { walkerRunner.Run(); } else { walkerRunner.Walk(); }
Am I losing my mind or is it defeating the goal of an interface called ICanWalkAndRun ?
Original post
I find it very peculiar that in .NET, when I develop a class with a collection property that provides random access by index (or a method that returns an indexed collection, etc.) , but should not or cannot be changed by adding / by removing elements , and if I want to “do the right thing” OOP-wise and provide an interface so that I can change the internal implementation without breaking the API, I need to go with IList<T> .
The standard approach, apparently, is to go with some implementation of IList<T> , which explicitly defines the Add , Insert methods, etc. - usually by doing something like:
private List<T> _items; public IList<T> Items { get { return _items.AsReadOnly(); } }
But I hate that. If another developer uses my class, and my class has a property of type IList<T> , and the whole idea of the interface is “these are some available properties and methods” , why should I throw a NotSupportedException (or whatever), when it trying to do something that, according to the interface, should be completely legal?
It seems to me that the implementation of the interface and the explicit definition of some of its members is the opening of a restaurant and the addition of some elements to the menu - perhaps in some obscure, easy to skip part of the menu, but in the menu nonetheless - which are simply never available.
It seems like there should be something like an IArray<T> interface that provides very simple random access by index, but does not add / remove, as shown below:
public interface IArray<T> { int Length { get; } T this[int index] { get; } }
And then IList<T> can implement ICollection<T> and IArray<T> and add its IndexOf , Insert and RemoveAt methods.
Of course, I could always just write this interface and use it myself, but this does not help with all the existing .NET classes that do not implement it. (And yes, I know that I can write a shell that accepts any IList<T> and spits out an IArray<T> , but ... seriously?)
Does anyone know why the interfaces in System.Collections.Generic were designed this way? Am I missing something? Are there any strong arguments against what I am saying about my problems, with the approach of explicitly defining IList<T> members?
I am not trying to seem impudent, as if I knew better than the people who developed the classes and .NET interfaces; it just doesn't make sense to me . But I am ready to admit that I probably did not take it into account.