8

Why List<T> implements IReadOnlyList<T> even though List<T> is not read only?

Colonel Panic
  • 132,665
  • 89
  • 401
  • 465
Doug
  • 6,322
  • 3
  • 29
  • 48
  • 1
    I think you're confusing the fundamentals of derivation and implementation. Classes derive from a base class, but they implement an interface. Keeping that in mind explains the differences between those two concepts. You're not saying `IList` is an `IReadonLyList`, just that it can be used as one. – itsme86 Feb 10 '14 at 19:23
  • 1
    @itsme86 It's actually a very difficult design decision to use the design that they did. Someone may reasonably expect that an `IReadOnlyList` will *never* change, that is to say that it's *immutable*, rather than to say that it's a reference to a *mutable* list that simply cannot be mutated using that reference (without casting). Because of this confusion there is actually some resistance to the decision for `List` to implement `IReadOnlyList`. – Servy Feb 10 '14 at 19:28
  • Good question. In my opinion it is a poor naming of the interface. It should have been called something like `IAccessableByIndexList`, although that is not very good either, but it doesn't fool you into thinking the collection must be read-only. – Magnus Feb 10 '14 at 19:33
  • That seems silly. You could have a class `Finger` that implemented an interface `IWritingUtencil` (that would only work in sand, snow, while bleeding?), but I wouldn't think of a finger as a writing utencil necessarily; just that it could be used as one. – itsme86 Feb 10 '14 at 19:38
  • 2
    I've always thought `IReadOnlyList` was an unfortunate misnomer. `IReadableList` would have been a preferred choice. I guess that's why it gets used so rarely. – Cory Nelson Feb 10 '14 at 19:46
  • Don't confuse read-only with immutable. Think about a read-only property, like the `Balance` property of an `Account` object. The property is read-only because it can't be changed through the property itself (you can't do `account.Balance = 1000000`. But the property can be changed through other mechanisms, such as `account.MakeDeposit(1000000)`. – Darryl Sep 23 '16 at 17:27

2 Answers2

9

It allows you to expose a read only "proxy" of that list, so that you can pass that interface reference elsewhere and know that the code won't mutate the list. (Technically it can try to cast it back to something like List and mutate it, but it shouldn't do that.)

It also allows a method to specifically indicate that while it needs to accept a list, it won't mutate it.

Having a read-only interface also allows that interface to be covariant, unlike List or IList.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • 1
    Disagree with the first paragraph. If you are *returning* a collection that should remain read-only, you should wrap it in a data structure that actually enforces read-only, not just declare the return type "IReadOnly". It's most useful as an *input* parameter type, where you need something indexable but not necessarily mutable (but you should not rely on the assumption that said input *isn't* mutable). It's simply a poorly named interface. – nmclean Feb 10 '14 at 19:43
  • 2
    @nmclean The first paragraph is addressing the exact same problem as the second, merely from the point of view of the caller, not the author. The caller is creating a proxy of their list which is read only that they are passing to something. – Servy Feb 10 '14 at 19:44
  • If the caller is solving the same problem (ensuring that external code won't mutate the list), then I disagree with the second point as well. If the caller wants to retain control of its collection, it should also wrap it before passing. A parameter type of IROL is not a promise. It simply means that it's the minimum interface required. – nmclean Feb 10 '14 at 19:59
1

You can think of this in terms of subtyping. A regular list can be a readonly list if you don't modify it, ie List<T> is a subtype of IReadOnlyList<T> (I'm not sure if the C# types actually bear that out). The typing allows you to specify that a particular piece of code won't make any changes to the list.

Another example of this kind of thing is in C where you can pass an int to a method that accepts a const int or a pass an int to a method that expects a volatile int. Treating the argument more stringently than is needed isn't harmful.

mrmcgreg
  • 2,754
  • 1
  • 23
  • 26