5

I am looking at the List<T>.AsReadOnly() method.

Since List<T> itself is a IReadOnlyCollection<T>. (It implements IReadOnlyList<T> and IReadOnlyList<T> implements IReadOnlyCollection<T>) the implementation of AsReadOnly() could technically be return this; which has no allocations.

Instead this does return new ReadOnlyCollection<T>(this);. As far as I can tell, ReadOnlyCollection<T> is a wrapper that forwards calls to the instance passed in its constructor.

So why is this creating a new object in this case when it could return itself, without allocations? What advantage does it get by using this separate wrapper object, in terms of perf/maintenance/backwards compatibility?

Is it to prevent the caller from casting it back to List and still make changes?

What am I missing here?

3
  • 5
    "Is it to prevent the caller from casting it back to List and still make changes?" Yep, probably.
    – Alexander
    Feb 28 at 1:51
  • they look identical on both List<T> and ReadOnlyCollection<T> ?
    – Madushan
    Feb 28 at 6:51
  • Note also that a list is an IReadonlyCollection<T>, while .AsReadOnly() returns a ReadOnlyCollection<T>, which is handy if the function you're trying to call accepts the class rather than the interface. Feb 28 at 12:31

2 Answers 2

6

If there was no allocation, there'd be no reason to have the AsReadOnly() function. If all you want to do is cast it to an IReadOnlyList, then do that:

var myList = new List<string>();
var myListReadOnly = (IReadOnlyList<string>) myList;

Casting rather than relying on a function with potential side effects (like creating an allocation) is more explicit about what you are doing.

If anything, I'd say that the AsReadOnly() function's name is a bit misleading, as it does not explicitly mention the use of ReadOnlyCollection.

Speaking on the motivation of this, I believe you are correct. Benefits of returning a ReadOnlyCollection include:

  • Prevents receivers of the collection casting it to a mutable list
  • Methods gotten via reflection will not work on the collection
  • Prevents erroneous pattern matching on .GetType()

The concept of ReadOnly is to protect the programmer from making a mistake manipulating the data. In most applications, the performance of a small allocation like this is trivial and is worth the benefit to not only communicate this data is ReadOnly, but to more actively enforce it.

As applications get more complex, many different code paths can lead to bizarre and unexpected use-cases. Take this example:

static void GatherAnimals()
{
    var myAnimals = new List<IAnimal> { new Cat(), new Dog() };
    var animalCollector = new MyAnimalCollector();
    animalCollector.Collect(myAnimals);
}

class MyAnimalCollector : BaseAnimalCollector
{
    // Called by the BaseAnimalCollector.Collect() method
    override void AddAnimals(object animals)
    {
        if (animals is IList<IAnimal> list)
        {
            list.Add(new Rabbit());
        }
    }
}

Let's say you as a developer do not have control over the implementation of BaseAnimalCollector, thus you cannot change parameter "animals" to a different type. Now, somewhere else in your code, you have a slightly different call:

static void GatherAllCats()
{
    var myAnimals = new List<IAnimal> { new Cat(), new Tiger() }.AsReadOnly();
    var animalCollector = new MyAnimalCollector();
    animalCollector.Collect(myAnimals);
}

Now the "is" call will return false, and the list will not be edited.

I would never recommend coding like the above example, but it shows how these kinds of bugs can sneak in.

Let's say it was critically important that a rabbit is not added to the Cat collection: by .AsReadOnly() returning a ReadOnlyCollection, it protects from that happening. If it just did a cast, it would not.

1
  • 1
    If I'm not mistaken, this could be implemented today with a struct, for the best of both worlds. No indirection, but not castable back to a mutable list, either
    – Alexander
    Feb 28 at 15:23
4

I think the simple reason is backward compatibility. ReadOnlyCollection<T> and List<T>.AsReadOnly() predates IReadOnlyList<T> by several versions.

  1. List<T>.AsReadOnly() exists all the way back to .Net framework 2.0
  2. IReadOnlyList<T> was introduced in .net framework 4.5

Changing the signature of List<T>.AsReadOnly() would break backward compatibility, and that is a big no-no. For new code I would strongly prefer IReadOnlyList<T>/IReadOnlyCollection<T>.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.