Search code examples
c#readonly-collection

Best way to expose ReadOnlyCollection of ReadOnlyCollection while still being able to modify it inside class


I don't know if this is possible, but basically, I want to expose a property of type ReadOnlyCollection<ReadOnlyCollection<MyType>>, while still being able to modify the underlying collection inside the class exposing the property. Further, I want users of my class to be able to hold a reference to the collection returned, so that it updates as I update it internally in the class exposing the property.

For instance, If this were a single collection, I could do something like this:

  public class MyClass {
    private List<MyType> _List;
    public ReadOnlyCollection<MyType> RList {
      get {
        return _List.AsReadOnly();
      }
    }
  }

Which would still allow for adding items to the list inside my class by doing _List.Add(). Further, if a client of this class was to do, say:

var collectionRef = myClassInstance.RList;

Then collectionRef would also change as I add elements to the list from inside MyClass.

The problem arises if I want a list of lists:

  private List<List<MyType>> _List;
    public ReadOnlyCollection<ReadOnlyCollection<MyType>> RList {
      get {
        return _List.AsReadOnly();
      }
    }

The immediate problem with the above attempt is that AsReadonly() only applies to the outer list. So I'd be trying to return a ReadOnlyCollection<List<MyItem>> which is not the declared type for the property. The only way I can think of to satisfy the declared property type would involve creating a new ReadOnlyCollection in a way similar to:

new ReadOnlyCollection<ReadOnlyCollection<MyItem>>(_List)

or

new ReadOnlyCollection<ReadOnlyCollection<MyItem>>() {
new ReadOnlyCollection<MyItem>(_List[0]),
new ReadOnlyCollection<MyItem>(_List[1]),
...
}

But I'm not sure what the syntax for creating this nested collection would be; Also, I'm not sure if by creating 'new' collections, I won't be able to track changes to the underlying lists by means of a ref to the value returned by the property. That is, if inside MyClass I do _List[1].Add(myTypeInstance) I'm not sure that someone holding a ref to the readonly version will see that new item.

I'm open to other approaches all together honesty. Basically I just need to expose a list of lists of items which is readonly but can be modified inside the class that exposes the property and for clients to be able to see the changes reflected without needing to get the value again from the property accessor.


Solution

  • ReadOnlyCollection cannot do this, at least not by itself. If you add a new list to _List, any user of RList will need to get a chance to see a new ReadOnlyCollection linked to that new list. ReadOnlyCollection simply doesn't support that. It is only a read-only view of the original items, and the original items are modifiable.

    You can implement a custom class which behaves similarly to ReadOnlyCollection, except that it doesn't return the original items, but wraps them. ReadOnlyCollection's implementation is trivial: pretty much all of ReadOnlyCollection's methods can be implemented in a single line: they either throw an unconditional exception (e.g. IList.Add), return a constant value (e.g. IList.IsReadOnly), or forward to the proxied container (e.g. Count).

    The change you would need to make to adapt it to your use involve the various methods that deal directly with items. Note that that is more than simply the this[int] indexer: you would also need to make sure two proxies of an identical list compare as equal, and provide a private/internal method to obtain the original list from a proxy, to make methods such as Contains work. You would also need to create a custom enumerator type to make sure GetEnumerator doesn't start returning the original items. You would need to create a custom CopyTo implementation. But even keeping those things in mind, it should be quite easy to do.


    That said, perhaps there is a somewhat less clean but easier approach: if you create a custom class MyReadOnlyCollection<T> derived from ReadOnlyCollection<T>, you can provide an internal Items member (which forwards to ReadOnlyCollection<T>'s protected Items member).

    You can then create a MyReadOnlyCollection<MyReadOnlyCollection<MyClass>>, and call RList[0].Items.Add from your own assembly, without worrying that external users will be able to call that too.


    As noted, if the outer list actually never changes, it can be simpler: in that case, you can simply do something like

    public ReadOnlyCollection<ReadOnlyCollection<MyType>> RList {
      get {
        return _List.ConvertAll(list => list.AsReadOnly()).AsReadOnly();
      }
    }
    

    which doesn't bother to monitor _List for changes.