【问题标题】:How would I initialize these two lists so that modifying one doesn't modify the other?我将如何初始化这两个列表,以便修改一个不会修改另一个?
【发布时间】:2011-05-30 02:45:53
【问题描述】:

我知道为什么会发生这种情况,但是有什么方法可以做到这一点而不必实现 ICloneable 或 Copy() 方法?最好是 .net 2.0,但如果需要,3.5 也可以。

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

所以我想保留两个列表。一个用于我将要修改的项目列表,另一个用于原始的、未修改的项目列表。这样,如果我需要撤消,我只需删除修改的项目并用原件替换它们。我尝试分配 _originalItems 变量的大多数方法都不起作用,那么我需要做什么?

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

【问题讨论】:

  • @Nicolas,请参阅 SLaks 上的 cmets。不幸的是,这还不足以满足我的需求。

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


【解决方案1】:

我建议使用不可变列表来解决撤销-重做问题。

不可变的数据结构是不会改变的。要将某些内容添加到不可变列表中,请在现有列表上调用 Add 方法并返回 一个新列表。因为新列表和旧列表都是不可变的,所以希望旧列表的大部分内存可以被新列表重用。

使用不可变的数据结构,撤消/重做很容易。您只需维护两个列表列表,列表的“撤消”列表和列表的“重做”列表。要撤消,您从撤消列表中取出第一个列表并将其放在重做列表中。要重做,你做相反的事情。这样您就不必担心撤消和重做所有这些突变;除了撤消和重做列表的值所引用的内容之外,没有任何变化。

有关 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
}

好的,你想要一个当前列表,比如说,整数和一个撤消重做队列。

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}

看,这个想法是因为每个列表都是不可变的,您在撤消和重做队列中维护当前的实际值,而不是拥有一个可变列表并且必须弄清楚如何将其变异回之前的状态。

诀窍在于提出一种可以重用其他数据结构内存的数据结构,因此存储 {null, {}, {1}, {1,2}} 实际上不会产生两个副本{1} 节点的。

一旦你有了不可变的数据,那么保持 整数列表 的 undo-redo 与 integersstrings 的 undo-redo 完全相同 或任何其他不可变数据类型。您只需存储状态,而不必担心有人会更改该状态。

【讨论】:

  • 感谢埃里克的链接。但是,“除了撤消和重做列表的值所引用的内容之外,没有任何变化。”有点问题。我不希望列表中的项目反映更改。我希望他们拥有我做突变之前的价值观。
  • @Brandon:我不确定我是否理解你的反对意见。我已添加说明。
  • 感谢您的更新。假设数据类型不是 int。它是 MyClass,它包含一个名为 Location 的属性。如果我要使用不可变列表,并添加一个位置为 5 的 MyClass 实例,但后来将位置更改为 10,这不会反映在列表中吗?列表是不可变的,但列表中的对象不是,所以即使我做了撤消,撤消列表的 MyClass 实例将包含 10 而不是 5?
  • 我仍然不确定这是否回答了我的(解释不清的)问题,但 +1 以获得详细和解释清楚的答案。感谢您花时间回答:)
  • @Brandon:那么你所做的就是让 MyClass 也成为不可变的。当您更改位置时,您不会改变现有 MyClass 的字段,而是创建一个具有不同字段值的全新 MyClass。然后,您可以将旧实例存储在 undo-redo 中。通过禁止几乎所有突变,您可以轻松地从一组快照中进行选择,即“当前”快照。
【解决方案2】:

您可以简单地再写一次new List&lt;SelectedItems&gt;(selectedItems)

这将创建一个引用相同实例的单独列表。

objects 的更改将在两个列表中看到(因为它们是相同的实例);不会对列表(例如Add())进行更改。

如果你想复制实例,你需要一个Copy() 方法; .Net 不能神奇地深度复制任意类型。

创建Copy 方法后,您可以编写

_originalItems = selectedItems.ConvertAll(o => o.Copy());

【讨论】:

  • 这就是我最初尝试的。它不起作用。 _originalItems 包含一个具有坐标信息的类。更改 _selectedItems 上的坐标会导致 _originalItems 也被修改。
  • 谢谢,这是有道理的。不是我希望的答案,但我想如果我别无选择......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-31
  • 2023-04-06
  • 2019-12-17
  • 1970-01-01
  • 2014-10-07
相关资源
最近更新 更多