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 than ReadOnlyCollection<T>
?
IReadOnlyList<T>
only provides the count
and the indexing operator [index]
(and IEnumerable<T>
), but if that's all you need you can write something like this:
public interface IReadOnlyMyItem
{
int SomeValue { get; }
}
public interface IWritableMyItem: IReadOnlyMyItem
{
new int SomeValue { get; set; } // Note use of `new`
}
public sealed class WriteableMyItem: IWritableMyItem
{
public int SomeValue { get; set; }
}
public sealed class MyClass
{
readonly List<WriteableMyItem> _items = new List<WriteableMyItem>();
public IReadOnlyList<IReadOnlyMyItem> MyItems() => _items;
}
Note that I split the interface into IReadOnlyMyItem
and IWritableMyItem
but you don't actually need IWritableMyItem
- you could just omit that interface and write:
public sealed class WriteableMyItem: IReadOnlyMyItem
{
public int SomeValue { get; set; }
}
instead. I just included IWritableMyItem
for completeness (because it shows how you introduce a set
to an interface property that only has a get
, by using new
).
As per the comments below, you can prevent the possibility of downcasting by making IWriteableMyItem
and WriteableMyItem
private or internal. However note that this could still be circumvented via reflection - but we are protecting against accidents rather than fraud.