41

When IReadOnlyList<T> was introduced in .NET 4.5, for a moment I thought the missing part of the puzzle was finally inserted in place: a way to pass a true readonly indexable interface where previously I would have to use my own read-only interfaces and create wrapper classes around everything.

I was expecting the interface to be placed inside the "natural" hierarchy, which would ideally be:

IEnumerable<T> 
.GetEnumerator()
      -> IReadOnlyCollection<T> : IEnumerable<T>
      .Count
            -> IReadOnlyList<T> : IReadOnlyCollection<T>
            .Item[...]
                     -> IList<T> : IReadOnlyList<T>
                     .Add(...)
                     .Clear()
                     .Contains(...)
                     (etc)

But, as it turns out, IList<T> doesn't inherit from IReadOnlyList<T>.

Is there a reason for this?

Some clarification:

Note that IReadOnlyList<T> is merely a contract which states that the list provides a way to get the list count and read the value at a certain index. It's poorly-named, because it doesn't enforce that the actual implementation is readonly.

A List<T> implements IEnumerable<T>, an IList<T> inherits from IEnumerable<T>, but this doesn't mean that these classes can only be enumerated.

So, if you want to pass a list to a method, and only allow it to be indexed (read), but not modified, you need to wrap it in a new instance. At the same time, you can pass it to a method which accepts IEnumerable<T> or IList<T> without having to wrap it. This is what I find to be broken.

I also believe the proper name should had been something like ICountable for IReadOnlyCollection and IIndexable for the IReadOnlyList:

IEnumerable<T> 
.GetEnumerator()
      -> ICountable<T> : IEnumerable<T>
      .Count
            -> IIndexable<T> : ICountable<T>
            .Item[...]
                     -> IList<T> : IIndexable<T>
                     .Add(...)
                     .Clear()
                     .Contains(...)
                     (etc)
Lou
  • 4,244
  • 3
  • 33
  • 72
  • 7
    As you already said `IReadonlyList` was introduced in .NET4.5 which is way later `IList` was added to .Net. – MakePeaceGreatAgain Mar 11 '16 at 11:26
  • 4
    Check this: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b4fb991a-3f5c-4923-93d4-7cd5c004f859/new-interfaces-ireadonlylist-and-ireadonlydictionary?forum=netfxbcl – w.b Mar 11 '16 at 11:27
  • 6
    `List<>` implements `IList<>` but it's not read-only, so why should it implement `IReadOnlyList<>`?. – Tim Schmelter Mar 11 '16 at 11:31
  • 3
    The question might have some wrong assumptions but it need not be down voted! Its still ok to ask. – Carbine Mar 11 '16 at 11:33
  • 2
    For what it's worth, `List` has an `AsReadOnly()` method that returns a `ReadOnlyCollection` which implements `IReadOnlyList`. – reduckted Mar 11 '16 at 11:33
  • @thirdwaffle: you can also use the [`ReadOnlyCollection<>`-constructor](https://msdn.microsoft.com/en-us/library/ms132476(v=vs.110).aspx) – Tim Schmelter Mar 11 '16 at 11:34
  • 14
    @TimSchmelter `List<>` actually implement `IReadOnlyList<>`. And this question does not ask about `List<>`, it ask about `IList<>`. – user4003407 Mar 11 '16 at 11:34
  • @TimSchmelter Yes, which is exactly what the `AsReadOnly` method does. – reduckted Mar 11 '16 at 11:36
  • 10
    This is actually so annoying, because I can't just write extension methods on `IReadOnlyList` and I can't write them for both `IList` and `IReadOnlyList` because then you get ambiguous method errors. Gah. – Mike Marynowski Sep 27 '17 at 20:13
  • 1
    @TimSchmelter: "so why should it implement `IReadOnlyList<>`" - because the latter is a *read-only view* on a list. It does not mean the list is actually constant or cannot be changed by anyone or anything else. – O. R. Mapper Jun 28 '18 at 21:54

1 Answers1

46

@w.b put a link to New interfaces IReadOnlyList and IReadOnlyDictionary in the comments that contains an answer:

Why did we not change the existing interfaces to extend the read-only interfaces?

It looks like a reasonable assumption that it works because the read-only interfaces are purely a subset of the read-write interfaces. Unfortunately, it is incompatible because at the metadata level every method on every interface has its own slot (which makes explicit interface implementations work).


Immo Landwerth | .NET Framework Team (BCL) | http://blogs.msdn.com/b/bclteam/

To explain this a bit more clearly:

Suppose that a program written for .NET 4.0 contains a class MyList<T> that implements IList<T>. It clearly cannot implement IReadOnlyList<T> as that interface doesn't exist.

Now suppose the system administrator installs .NET 4.5 and suppose that .NET 4.5 made IList<T> implement IReadOnlyList<T>.

If the program would then be loaded, the runtime would detect that MyList<T> claims to implement IList<T>, but doesn't actually implement all the methods: it doesn't implement IReadOnlyList<T>'s methods. The program would no longer work.

The C# compiler might be able to match the methods by name, but the runtime doesn't do this. Since .NET 4.5 was supposed to have backwards binary compatibility, interfaces couldn't be extended to implement other interfaces, not even if those other interfaces contain a strict subset of the required methods.

Community
  • 1
  • 1
  • 4
    +1 And another example based on the quote's reference to explicit implementation. If a class already has an explicit `int IList.Count ...` implementation and `IList` would inherit from `IReadOnlyList`, Count would belong to the latter and the existing implementation would fail, even if build with the correct framework. – Me.Name Mar 11 '16 at 12:32
  • @Me.Name That's a good example too, although I suppose that one might have been solvable with an extension to C#. –  Mar 11 '16 at 12:35
  • +1 Well crap, I presumed it would be something like this. I guess it's good enough to have `List` and `T[]` implement it. – Lou Mar 11 '16 at 12:54
  • 2
    Will .net-5.x be willing to break the ABI and fix this? – binki Jun 21 '16 at 14:43
  • 2
    @binki As I understand it, what would have been .NET 5 has been rebranded .NET Core (but someone can correct me if I'm wrong and that's actually something else). In .NET Core, [`IList`](https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Collections/Generic/IList.cs) still doesn't currently implement `IReadOnlyList`. –  Jun 21 '16 at 16:57
  • 2
    @hvd: I figure one of the goals of .NET Core is to keep API compatibility wherever reasonable, so that it's as easy as possible to migrate **code** (if not binaries) to / from the full framework, but I'm not sure... – Joe Amenta Jan 20 '17 at 13:15
  • `IList` vs `IReadOnlyList, and `ICollection` vs `IReadOnlyCollection` is screwed up in .NET. Please see [this](https://github.com/dotnet/corefx/issues/23578). – Shimmy Weitzhandler Jan 25 '18 at 00:30
  • How could this fix break API compatibility? – Noein Jan 12 '20 at 16:14
  • Thanks for the good explanation! Since we have default implementations for interfaces from C# 8.0 onwards, I guess there would be a easy fix for this issue nowadays, if `IList` would return it's own `Count` property as default for `IReadOnlyList.Count` etc. , or am I missing something? – v01pe Apr 12 '23 at 09:17