I have a class which contains a Collection (Collection<MyItem> myItems
). I want to be able to modify this Collection privately and return it as read-only through a public property (in O(1) if possible). I was thinking about using a ReadOnlyCollection
, but the problem is the items (MyItem
) are not read-only. So I created a read-only wrapper class (ReadOnlyMyItem
). How can I make the ReadOnlyCollection
return the wrapper class (ReadOnlyMyItem
) through the square brackets operator without building the entire Collection again, I want everything to remain O(1).
I was thinking about creating a new class derived from ReadOnlyCollection
(class ReadOnlyMyCollection : ReadOnlyCollection<MyItem>
or class ReadOnlyMyCollection : ReadOnlyCollection<ReadOnlyMyItem>
) which returns the read-only wrapper class (ReadOnlyMyItem
) through the square brackets operator, but either the square brackets operator is not marked as virtual and the return type is not even the same or the constructor doesn't have an IList
of the wrapper class (IList<ReadOnlyMyItem>
) to use. Should I build the class (ReadOnlyMyCollection
) from scratch?
The code looks something like this:
public class MyClass
{
public ReadOnlyCollection<ReadOnlyMyItem> MyItems { get => GetMyItems(); }
private List<MyItem> myItems;
// ...
private ReadOnlyCollection<ReadOnlyMyItem> GetMyItems()
{
throw new NotImplementedException();
}
// ...
}
public class MyItem
{
// ...
}
public class ReadOnlyMyItem
{
private readonly MyItem myItem;
// ...
public ReadOnlyMyItem(MyItem myItem) => this.myItem = myItem;
// ...
}
Can you get away with using
IReadOnlyList<T>
rather thanReadOnlyCollection<T>
?IReadOnlyList<T>
only provides thecount
and the indexing operator[index]
(andIEnumerable<T>
), but if that's all you need you can write something like this:Note that I split the interface into
IReadOnlyMyItem
andIWritableMyItem
but you don't actually needIWritableMyItem
- you could just omit that interface and write:instead. I just included
IWritableMyItem
for completeness (because it shows how you introduce aset
to an interface property that only has aget
, by usingnew
).As per the comments below, you can prevent the possibility of downcasting by making
IWriteableMyItem
andWriteableMyItem
private or internal. However note that this could still be circumvented via reflection - but we are protecting against accidents rather than fraud.