我知道为什么会发生这种情况,但有没有办法在不必实现ICloneable或Copy()方法的情况下执行此操作?最好是.net 2.0,但如果有必要,则3.5可以。
基本上我正在尝试实现撤消方法。在大多数情况下,我可以在Undo()
中执行反向操作,但对于其他不可能的操作。
所以我想保留两个清单。一个用于我将要修改的项目列表,另一个用于原始未修改的项目列表。这样,如果我需要进行撤消,我只需删除已修改的项目并将其替换为原始项目。我尝试分配_originalItems变量的大部分方法都不起作用,那么我需要做什么呢?
public MyClass(List<SelectedItems> selectedItems)
{
_selectedItems = new List<SelectedItems>(selectedItems);
_originalItems = ??
}
答案 0 :(得分:5)
您可以再次写new List<SelectedItems>(selectedItems)
。
这将创建一个引用相同实例的单独列表。
对象的更改将在两个列表中看到(因为它们是相同的实例);对列表(例如Add()
)的更改不会。
如果要复制实例,则需要Copy()
方法; .Net不能神奇地深层复制任意类型。
创建Copy
方法后,您可以编写
_originalItems = selectedItems.ConvertAll(o => o.Copy());
答案 1 :(得分:3)
我建议使用不可变列表来解决undo-redo问题。
不可变数据结构是不会改变的结构。要向不可变列表添加内容,可以在现有列表上调用Add方法,然后返回新列表。因为新列表和旧列表都是不可变的,所以旧列表的大部分内存都可以被新列表重用。
使用不可变数据结构,undo / redo很容易。您只需维护两个列表列表,列表的“撤消”列表和列表的“重做”列表。要撤消,请从撤消列表中取出第一个列表并将其放在重做列表中。要重做,你会做相反的事情。这样你就不必担心撤消和重做所有这些突变;除了撤消和重做列表的值引用之外,没有任何突变。
关于C#中不可变数据结构的一些其他想法,请参阅我关于该主题的文章:
http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/
更新:
我不希望列表中的项目反映更改。我希望他们在我做突变之前拥有他们拥有的价值
我不确定我是否理解这条评论。让我勾勒出我的意思。
假设您有一个不可变列表:
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
}
好的,你想要一个当前的列表,例如,整数和一个undo-redo队列。
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;
}
}
现在你可以说
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));
所以操作如下:
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}
请注意,这个想法是因为每个列表都是不可变的,你在undo和redo队列中维护当前的实际值,而不是拥有一个可变列表并且必须弄清楚如何把它变回以前的状态。
诀窍在于提出一种可以重用其他数据结构内存的数据结构,以便存储{null,{},{1},{1,2}}实际上不会产生两个副本{1}节点的。
一旦有了不可变数据,那么保持整数列表的undo-redo与整数或字符串的undo-redo完全相同,或任何其他不可变数据类型。您只需存储状态而不必担心有人会改变该状态。