I know it is an old problem, however I faced the same issue today.
Background: I want to store data in my application, e.g. users can set their custom objects in the project and the Undo-redo mechanism must be adapted to handle batched data storing.
My approach:
I created some interfaces, and I made a wrapper for the collection I don't want the users to modify.
public class Repository : IRepository
{
// These items still can be changed via Items[0].CustomProperty = "asd"
public readonly List<CustomItem> Items { get; }
private readonly List<CustomItem> m_Originaltems;
//However, when I create RepositoryObject, I create a shadow copy of the Items collection
public Repository(List<CustomItem> items)
{
items.ForEach((item) =>
{
// By cloning an item you can make sure that any change to it can be easily discarded
Items.Add((CustomItem)item.Clone());
});
// As a private field we can manage the original collection without taking into account any unintended modification
m_OriginalItems = items;
}
// Adding a new item works with the original collection
public void AddItem(CustomItem item)
{
m_OriginalItems.Add(item);
}
// Of course you have to implement all the necessary methods you want to use (e.g. Replace, Remove, Insert and so on)
}
Pros:
Using this approach you basically just wraps the collection into a custom object. The only thing you expect from the user side is to have ICloneable
interface implemented.
If you want, you can make your wrapper generic as well, and give a constraint like where T : ICloneable
Cons:
If you add new items, you won't know about them by checking the Items property. A workaround can be done by creating a copy of the collection whenever Items.get()
is called. It is up to you and your requirements.
I meant something like this:
public class Repository : IRepository
{
public List<CustomItem> Items => m_OriginalItems.Select(item => (CustomItem)item.Clone()).ToList();
private readonly List<CustomItem> m_Originaltems;
public Repository(List<CustomItem> items)
{
m_OriginalItems = items;
}
public void AddItem(CustomItem item)
{
m_OriginalItems.Add(item);
}
// Of course you still have to implement all the necessary methods you want to use (e.g. Replace, Count, and so on)
}