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

发布于 2024-10-09 03:27:12 字数 479 浏览 0 评论 0原文

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

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

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

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

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.

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 = ??
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

梦中楼上月下 2024-10-16 03:27:12

您只需再次编写 new List(selectedItems) 即可。

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

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

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

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

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

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());
琉璃梦幻 2024-10-16 03:27:12

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

不可变数据结构是一种不会改变的数据结构。要向不可变列表添加内容,您可以在现有列表上调用 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} 节点。

一旦拥有不可变数据,那么保留整数列表的撤消重做就与整数字符串的撤消重做完全相同>,或任何其他不可变的数据类型。您只需存储状态,而不必担心有人会更改该状态。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文