I suggest you ...

Make IList<T> inherited from IReadOnlyList<T>

It satisfies Liskov substitution principle.
Example:

void SomeMethod(IReadOnlyList<T> list)
{/*...*/}

void MyMethod(IList<T> list)
{
//do something with list, add/remove elements etc.
SomeMethod(list);//PROBLEM
}

Currently IList<T> cannot be passed to SomeMethod. But IReadOnlyList<T> consists of a subset of methods from IList<T> with the same signature and semantics. I feel it would be much better to make this explicit by inheriting IList<T> from IReadOnlyList<T>.

143 votes
Vote
Sign in
Check!
(thinking…)
Reset
or sign in with
  • facebook
  • google
    Password icon
    Signed in as (Sign out)
    You have left! (?) (thinking…)
    Artem Elkin shared this idea  ·   ·  Flag idea as inappropriate…  ·  Admin →

    8 comments

    Sign in
    Check!
    (thinking…)
    Reset
    or sign in with
    • facebook
    • google
      Password icon
      Signed in as (Sign out)
      Submitting...
      • Joe Amenta commented  ·   ·  Flag as inappropriate

        As others have pointed out, this really is a breaking change. There's basically zero chance of it being done in the full framework. "Basically zero" means it's hypothetically possible, but the options for how to do it feel pretty insane (to me) for the very small set of minor use cases where this would be desirable.

        For a bit more details, the main breaking case I can think of is that if you have a class that implements IList<T> explicitly (i.e., "int IList<T>.Count { get { return 4; } }"), then this change as desired breaks your class because Count is on IReadOnlyList<T>.

        The workaround that works in probably 90% of cases: just have your collection class implement both interfaces (where the implementation of IList<T> methods that modify the collection are all implemented explicitly to throw NotSupportedException). e.g., https://goo.gl/jejFTH. As far as I've seen, the BCL team did a really good job at this for the BCL collections where it made sense (the IReadOnlyFoo<T> interfaces are really easy to implement, especially if you're already implementing IFoo<T>).

        I guess you could also make a sub-interface that implements or composes both IList<T> and IReadOnlyList<T> and pass that around in your app.

        If you have no choice but to call a method with IReadOnlyList<T> in its signature given absolutely nothing but an IList<T> instance, you can always just allocate a ReadOnlyCollection<T> instance at the method call boundary to wrap the IList<T> you were given. Allocations are not free, but they're probably cheap enough for most use cases. You can even just try-cast as a last-ditch effort to avoid the allocation cost in many situations if performance is important to you (if the performance of the try-cast is unacceptable, then you should probably consider your use of interfaces in the first place, I'm guessing).

        There are just so many workarounds for such a small wart, where the alternative is such an annoying thing, that it really feels like a no-brainer (especially at this point) not to pursue this one further.

        Though to echo what others have said, this would in fact be a desirable and consistent thing to have if the framework were written from the ground up. "Read only" = "I will ONLY need to READ from this list", as opposed to "this instance ONLY supports procedures that READ the data", which is in the realm of System.Collections.Immutable.

      • zeblon commented  ·   ·  Flag as inappropriate

        Change the wording to what the interface actually represents and it’s easier to see the “is a” relationship. An IReadOnlyList represents a list with read capabilities, so for the sake of this discussion lets rename it IListWithReadCapabilities. An IList *absolutely* “is a” IListWithReadCapabilities. The .NET team has stated elsewhere that the only reason IList doesn’t inherit from IReadOnlyList is because it would break existing code that implements IList. It’s unfortunate, as it severely limits the usefulness of IReadOnlyList.

      • anon commented  ·   ·  Flag as inappropriate

        "And so read-write list is more specialized type of read-only list."

        Whatever you've been smoking, I'll pass.

        Read-only means you can only read. A Read-Write list is not a Read-Only list. It's terrible OO practice to simply slap inheritance into your structure because one class has a subset of operations of another. Inheritance is supposed to represent an "is a" relationship, and a writable list is not a read-only list.

      • Artem Elkin commented  ·   ·  Flag as inappropriate

        Read-only != Immutable. Yes, this is a bit misleading and I would prefer it is named IListView or something like that. But this doesn't contradict OO because this doesn't violate LSP. And so read-write list is more specialized type of read-only list.

      • Anonymous commented  ·   ·  Flag as inappropriate

        I don't think this is a good idea from object oriented perspective - read-write list is not more specialized type of read-only list, it is completely different type of list (at least as far as we care about class names).

      • MuiBienCarlota commented  ·   ·  Flag as inappropriate

        Since integration of concurrency with TPL and it's inclusion in C# 5 language with async/await, a global rethinking of collections seems to be under way: Immutable Collections comes in preview in BCL and Microsoft research's paper "Uniqueness and Reference Immutability for Safe Parallelism" are good examples.

      • David Kean commented  ·   ·  Flag as inappropriate

        Thanks for the suggestion. Unfortunately, we can't do it without breaking existing applications, it's a breaking change to add an additional interface to an another interface.

        David Kean
        BCL Team

      Feedback and Knowledge Base