3

Given the below: Is it truly the case that if an IReadOnly(of T) was conceived from an extant MutableEquivalent(of T), that simply casting to the mutable version allows full access, and changes to an object others may rely on to have static elements/count? The output below shows my "THIRD INJECTION" string, despite iterating through a read only collection. Is it just manners and self control that prohibit casting a readonly collection down? Are you supposed to see the interface provided, and bear its restrictions, cast-invariant, due to type alone? Thanks for any clarification on what this interface and others like it truly promise/guarantee.

EDIT- This is not a duplicate of IReadOnlyCollection<T> vs List.AsReadOnly() since I didn't mention List.AsReadOnly() in my question, nor IReadOnlyCollection. Those were introduced by responders. This was a question on underlying mutable list exposure when behind an IReadOnly interface. There are some overlaps in some comments, but a question such as "What does IReadOnly actually do for its underlying mutable List(of T)?" duplicates the spirit of my question. Not "What is the difference between these two entities pertaining to read only?", since I only mentioned one.

static class Program
{

    public static void Main()
    {
        List<string> hack = (List<string>) READONLY;
        hack.Add("THIRD INJECTION");

        foreach (var s in READONLY)
        {
            Console.WriteLine(s);
        }
    }

    public static readonly IReadOnlyList<string> READONLY = new List<string>{"@0", "@1"};

}
schulmaster
  • 413
  • 5
  • 16
  • 1
    Yes and even ReadOnlyCollection implements IList, throwing an exception if you try to mutate the collection – vc 74 Aug 10 '17 at 06:02
  • 4
    `IReadOnlyList` isn't a protection, it is a kind of contract saying "you're not allowed to modify this collection". If the developer wants to get around it, well he can. Just like you can access private fields of a collection by using reflection, or modify the value of `string.Empty`, or just anything really since it's your memory space. The point is, you can't do it involuntarily. If the developer decides not to respect the contract, he's on his own, he probably has a good reason to do so and knows the consequences – Kevin Gosse Aug 10 '17 at 06:03
  • @KevinGosse Thanks for your reply. I didn't realize the interface was more a loose protection for a programmer rather than a 'const'-esque' barrier to mutation. The Reflection analogy is poignant. Like I said below, C++ private inheritance came to mind. The compiler will prevent any upcast , explicit or implicit, from a private inheriting child class. I assumed, incorrectly, that a downcast from a readonly to mutable would be an invalid cast. – schulmaster Aug 10 '17 at 06:10
  • 1
    Use [List.AsReadOnly()](https://msdn.microsoft.com/library/e78dcd75(v=vs.110).aspx) and it will wrap the list into a `ReadOnlyCollection` which you cannot cast to a `List` – Sir Rufo Aug 10 '17 at 06:17
  • Possible duplicate of [List.AsReadOnly() vs IReadOnlyCollection](https://stackoverflow.com/questions/17398428/listt-asreadonly-vs-ireadonlycollectiont) – mjwills Aug 10 '17 at 08:01

2 Answers2

4

From your comment:

I didn't realize the interface was more a loose protection for a programmer rather than a 'const'-esque' barrier to mutation. … I assumed, incorrectly, that a downcast from a readonly to mutable would be an invalid cast.

An interface isn't intended as "protection" of any sort, at all. It is simply a promise to implement specific features.

The benefit of IReadOnlyList<T> is one of semantics and now, with generic type variance in C#, flexibility. The semantic benefit allows you to expose a list in a way that expresses the intent to only read it and not modify it.

The flexibility comes in, because the type parameter can be made covariant. This allows implicit conversions from IReadOnlyList<T1> to IReadOnlyList<T2> where T1 inherits T2.

It is up to the implementor of the interface to decide whether true immutability is provided or not. If you want a collection to be truly immutable, you would use ReadOnlyCollection<T> as the implementation for IReadOnlyList<T>, instead of List<T>. You can pass any IList<T> implementation to the ReadOnlyCollection<T> constructor, and the new object will expose that IList<T> object's data without allowing modification (directly…you can always, of course, cheat with reflection, even with a object that is truly immutable and not just a wrapper like ReadOnlyCollection<T>).

With any interface, you are always only dealing with a specific object, which has a specific type, that type always implements some functionality, and can always be cast to the original type, as well as any other interfaces it might implement.

It would be a mistake to think of interfaces as in any significant way a form of protection. They aren't. They just express what an object is able to do.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
1

The underlying collection of READONLY is a mutable list, this is the reason that the cast actually succeeds when creating the hack variable. READONLY is read-only for any of its consumers, but the underlying list can still be mutated.

UPDATED: As Sir Rufo pointed out, exposing the list by way of list.AsReadOnly() will prevent hard casting.

Mihai Pantea
  • 360
  • 3
  • 8
  • I realize why programmatically it occurs, but I'm uneasy as to why semantically. Coming from C++, when I inherited privately, I forgo the ability to be upcast to my base. I felt this interface would offer similar protection. Let's say I have a private mutable list, but I only want it publicly available through a readonly interface: could an external user simply guess(and verify with "is") that its underlying collection is extant and mutable, cast appropriately, and wreak havoc on my implementation? – schulmaster Aug 10 '17 at 06:06
  • 1
    @schulmaster Yes, `(READONLY as (IList))?.Add(...)` would work in your case – vc 74 Aug 10 '17 at 06:07
  • 1
    A consumer of your API could cast your returned `IReadOnlyList` to a `List` and succeed if the actual concrete type was `List`. Exposing the collection as `IReadOnlyList` expresses intent of use. It can't prevent forced casting. – Mihai Pantea Aug 10 '17 at 06:10
  • There are ways to still attain your desired behavior though, for example `IEnumerable` is also a read-only collection, that can be created through a generator mechanism, i.e. `yield return`. You can't cast the resulting `IEnumerable` to an `IList` – Mihai Pantea Aug 10 '17 at 06:16
  • @MihaiPantea That solution forgoes random access though, correct? – schulmaster Aug 10 '17 at 06:20
  • 1
    Right, but then again, have you tried the `ReadOnlyCollection`?. It might be just what you've been looking for. Checkout [how to expose a mutable list as read-only while still preventing casting](https://stackoverflow.com/questions/984042/read-only-list-or-unmodifiable-list-in-net-4-0) – Mihai Pantea Aug 10 '17 at 06:23
  • @MihaiPantea This might be a bad time to mention this, but my specific case that spawned this question was Dictionary -based, and its IReadOnlyDictionary interface. I simplified for StOv since my real throw was the read only interface, not my specific problem. – schulmaster Aug 10 '17 at 06:32
  • I guess you can refer to https://stackoverflow.com/questions/678379/is-there-a-read-only-generic-dictionary-available-in-net, specifically the answer detailing `ReadOnlyDictionary` – Mihai Pantea Aug 10 '17 at 06:41