How would I initialize these two lists so that modifying one doesn't modify the other?
I'm aware of why this is happening, but is there any way to do this without having to implement ICloneable or a Copy() method? Preferably .net 2.0, but 3.5 is fine if it is necessary.
开发者_JAVA百科Essentially I'm trying to implement an undo method. In most cases I can just perform the reverse action in the Undo()
, but for others that is not possible.
So I want to keep two lists. One for the list of items that I will be modifying, and one for the original, unmodified list of items. This way if I need to do an undo, I just delete the modified items and replace them with the originals. Most of the ways I've tried to assign the _originalItems variable doesn't work, so what would I need to do?
public MyClass(List<SelectedItems> selectedItems)
{
_selectedItems = new List<SelectedItems>(selectedItems);
_originalItems = ??
}
You can simply write new List<SelectedItems>(selectedItems)
a second time.
This will create a separate list that references the same instances.
Changes to the objects will be seen in both lists (since they're the same instances); changes to the lists (such as Add()
) will not.
If you want to copy the instances, you'll need a Copy()
method; .Net cannot magically deep-copy an arbitrary type.
Once you create a Copy
method you can write
_originalItems = selectedItems.ConvertAll(o => o.Copy());
I recommend using immutable lists for solving the undo-redo problem.
An immutable data structure is one that does not change. To add something to an immutable list, you call an Add method on the existing list and you get back a new list. Because the new list and the old list are both immutable, hopefully much of the memory of the old list can be re-used by the new list.
With immutable data structures, undo/redo is easy. You just maintain two lists of lists, the "undo" list of lists and the "redo" list of lists. To undo, you take the first list off the undo list and put it on the redo list. To redo, you do the opposite. That way you don't have to worry about undoing and redoing all these mutations; there are no mutations except in what the values of the undo and redo lists reference.
For some additional thoughts on immutable data structures in C#, see my articles on the subject:
http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/
UPDATE:
I don't want the items in the list to reflect changes. I want them to have the values they had before I did the mutations
I'm not sure I understand the comment. Let me sketch out what I mean.
Suppose you have an immutable list:
interface IImmutableList<T>
{
public IImmutableList<T> Append(T newItem);
public IImmutableList<T> RemoveLast();
public T LastItem { get; }
// and so on
}
sealed class ImList<T> : ImmutableList<T>
{
public static ImList<T> Empty = whatever;
// etc
}
OK, you want to have a current list of, say, ints, and an undo-redo queue.
sealed class UndoRedo<T>
{
T current = default(T);
IImmutableList<T> undo = ImList<T>.Empty
IImmutableList<T> redo = ImList<T>.Empty;
public T Current
{
get { return current; }
set
{
undo = undo.Append(current);
redo = ImList<T>.Empty;
current = value;
}
}
public void Undo()
{
var newCurrent = undo.LastItem;
undo = undo.RemoveLast();
redo = redo.Append(current);
current = newCurrent;
}
public void Redo()
{
var newCurrent = redo.LastItem;
undo = undo.Append(current);
redo = redo.RemoveLast();
current = newCurrent;
}
}
Now you can say
UndoRedo<IImmutableList<int>> undoredo = new UndoRedo<IImmutableList<int>>();
undoredo.SetCurrent(ImList<int>.Empty);
undoredo.SetCurrent(undoRedo.Current.Add(1));
undoredo.SetCurrent(undoRedo.Current.Add(2));
undoredo.SetCurrent(undoRedo.Current.Add(3));
undoredo.Undo();
undoredo.Undo();
undoredo.Redo();
undoredo.SetCurrent(undoRedo.Current.Add(4));
So the operations go like this:
Start: undo: {} redo: {} curr: null
Set: undo: {null} redo: {} curr: {}
Add 1: undo: {null, {}} redo: {} curr: {1}
Add 2: undo: {null, {}, {1}} redo: {} curr: {1, 2}
Add 3: undo: {null, {}, {1}, {1, 2}} redo: {} curr: {1, 2, 3}
Undo: undo: {null, {}, {1}} redo: {{1, 2, 3}} curr: {1, 2}
Undo: undo: {null, {}} redo: {{1, 2, 3}, {1, 2}} curr: {1}
Redo: undo: {null, {}, {1}} redo: {{1, 2, 3}} curr: {1, 2}
Add 4: undo: {null, {}, {1, 2}} redo: {} curr: {1, 2, 4}
See, the idea is because each list is immutable, you maintain in the undo and redo queues the actual values of current as they were, rather than having one mutable list and having to figure out how to mutate it back into its previous state.
The trick is in coming up with a data structure that can re-use the memory of other data structures, so that storing {null, {}, {1}, {1,2}} does not actually make two copies of the {1} node.
Once you have immutable data, then keeping an undo-redo of lists of integers becomes exactly the same as an undo-redo of integers, or strings, or any other immutable data type. You simply store state without worrying that someone is going to change that state.
精彩评论