我如何初始化这两个列表,以便修改一个不修改另一个?

时间:2010-12-23 18:25:41

标签: c# winforms .net-3.5 .net-2.0

我知道为什么会发生这种情况,但有没有办法在不必实现ICloneable或Copy()方法的情况下执行此操作?最好是.net 2.0,但如果有必要,则3.5可以。

基本上我正在尝试实现撤消方法。在大多数情况下,我可以在Undo()中执行反向操作,但对于其他不可能的操作。

所以我想保留两个清单。一个用于我将要修改的项目列表,另一个用于原始未修改的项目列表。这样,如果我需要进行撤消,我只需删除已修改的项目并将其替换为原始项目。我尝试分配_originalItems变量的大部分方法都不起作用,那么我需要做什么呢?

public MyClass(List<SelectedItems> selectedItems)
{
  _selectedItems = new List<SelectedItems>(selectedItems);
  _originalItems = ??
}

2 个答案:

答案 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完全相同,或任何其他不可变数据类型。您只需存储状态而不必担心有人会改变该状态。