3

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.

SaldaVonSchwartz
  • 3,769
  • 2
  • 41
  • 78

2 Answers2

0

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.

  • But if I ReadOnlyCollection is a read-only version of a normal collection, couldn't I somehow create one ReadOnlyCollection for each list inside the main list, and then create a ReadOnlyCollection out of that main list of lists? After that, I could add and remove items of the "internal" lists and as long as the number of "internal" leasts is not modified (only items inside them), I could expose a read-only list of read-only lists. Maybe? – SaldaVonSchwartz Jul 07 '14 at 06:24
  • @SaldaVonSchwartz Yes, if you never change the outer list, that will work. If you do ever change the outer list, though, regardless of how you're changing it (adding a list, removing a list, changing a list to a new list instance), it'll break. And you do mention `_List.Add()` in your question, which would be one of the things that breaks it. –  Jul 07 '14 at 06:40
  • Maybe I wasn't super clear in my example :/ I mean the "list of items" approach would work and so was asking if the list of fixed lists of variable items approach would work in a similar way, which I believe you just said would work. In that case, would you mind editing your answer to show how to initialize /construct a ReadOnlyCollection of a fixed number of ReadOnlyCollections each of variable items so I can mark it as correct? – SaldaVonSchwartz Jul 07 '14 at 08:27
  • @SaldaVonSchwartz (Forgot to notify you) Sure, done. If any of the inner lists change, that will be reflected in any already-retrieved collection. –  Jul 07 '14 at 12:35
  • Property get methods in general should not allocate memory, or perform potentially long iteration operations. If this was an ordinary method and not a property, the expense would be more obvious to users. – Frank Hileman Dec 19 '19 at 17:59
-1

Use Private Set to modify the underlying collection inside the class exposing the property

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

Use Events To notify other classes that collection has been modified

I am adding code in case above link expires. :/

Example

The following simple example shows a class, ListWithChangedEvent, which is similar to the standard ArrayList class, but also invokes a Changed event whenever the contents of the list change. Such a general-purpose class could be used in numerous ways in a large 
program.

// events1.cs
using System;
namespace MyCollections 
{
   using System.Collections;

   // A delegate type for hooking up change notifications.
   public delegate void ChangedEventHandler(object sender, EventArgs e);

   // A class that works just like ArrayList, but sends event
   // notifications whenever the list changes.
   public class ListWithChangedEvent: ArrayList 
   {
      // An event that clients can use to be notified whenever the
      // elements of the list change.
      public event ChangedEventHandler Changed;

      // Invoke the Changed event; called whenever list changes
      protected virtual void OnChanged(EventArgs e) 
      {
         if (Changed != null)
            Changed(this, e);
      }

      // Override some of the methods that can change the list;
      // invoke event after each
      public override int Add(object value) 
      {
         int i = base.Add(value);
         OnChanged(EventArgs.Empty);
         return i;
      }

      public override void Clear() 
      {
         base.Clear();
         OnChanged(EventArgs.Empty);
      }

      public override object this[int index] 
      {
         set 
         {
            base[index] = value;
            OnChanged(EventArgs.Empty);
         }
      }
   }
}

namespace TestEvents 
{
   using MyCollections;

   class EventListener 
   {
      private ListWithChangedEvent List;

      public EventListener(ListWithChangedEvent list) 
      {
         List = list;
         // Add "ListChanged" to the Changed event on "List".
         List.Changed += new ChangedEventHandler(ListChanged);
      }

      // This will be called whenever the list changes.
      private void ListChanged(object sender, EventArgs e) 
      {
         Console.WriteLine("This is called when the event fires.");
      }

      public void Detach() 
      {
         // Detach the event and delete the list
         List.Changed -= new ChangedEventHandler(ListChanged);
         List = null;
      }
   }

   class Test 
   {
      // Test the ListWithChangedEvent class.
      public static void Main() 
      {
      // Create a new list.
      ListWithChangedEvent list = new ListWithChangedEvent();

      // Create a class that listens to the list's change event.
      EventListener listener = new EventListener(list);

      // Add and remove items from the list.
      list.Add("item 1");
      list.Clear();
      listener.Detach();
      }
   }
}

OutPut

This is called when the event fires.
This is called when the event fires.
Faisal
  • 364
  • 1
  • 8