Как бы я инициализировал эти два списка, чтобы изменение одного не изменило другой? - PullRequest
2 голосов
/ 23 декабря 2010

Я знаю, почему это происходит, но есть ли способ сделать это без необходимости реализации метода ICloneable или Copy ()? Предпочтительно .net 2.0, но 3.5 подойдет, если это необходимо.

По сути, я пытаюсь реализовать метод отмены. В большинстве случаев я могу просто выполнить обратное действие в Undo(), но для других это невозможно.

Итак, я хочу сохранить два списка. Один для списка предметов, которые я буду изменять, и один для оригинального, неизмененного списка предметов. Таким образом, если мне нужно отменить изменения, я просто удаляю измененные элементы и заменяю их оригиналами. Большинство способов, которыми я пытался присвоить переменную _originalItems, не работают, так что мне нужно сделать?

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

Ответы [ 2 ]

5 голосов
/ 23 декабря 2010

Вы можете просто написать new List<SelectedItems>(selectedItems) во второй раз.

Это создаст отдельный список, который ссылается на те же экземпляры.

Изменения в объектах будут видны в обоих списках (так как они одинаковые экземпляры); изменения в списках (например, Add()) не будут.

Если вы хотите скопировать экземпляры , вам потребуется метод Copy(); .Net не может волшебным образом копировать произвольный тип.

Как только вы создадите Copy метод, вы можете написать

_originalItems = selectedItems.ConvertAll(o => o.Copy());
3 голосов
/ 24 декабря 2010

Я рекомендую использовать неизменяемые списки для решения проблемы отмены повторения.

Неизменяемая структура данных - это структура, которая не изменяется. Чтобы добавить что-то в неизменяемый список, вы вызываете метод Add в существующем списке и получаете обратно новый список . Поскольку новый список и старый список являются неизменяемыми, мы надеемся, что большая часть памяти старого списка может быть использована новым списком.

С неизменяемыми структурами данных отменить / повторить легко. Вы просто поддерживаете два списка списков: список отмененных списков и список повторных списков. Чтобы отменить, вы берете первый список из списка отмен и помещаете его в список повторов. Чтобы повторить, вы делаете противоположное. Таким образом, вам не нужно беспокоиться об отмене и повторении всех этих мутаций; нет никаких мутаций, кроме тех, на которые ссылаются значения списков отмены и повторения.

Дополнительные сведения о неизменных структурах данных в C # см. В моих статьях на эту тему:

http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/

UPDATE:

Я не хочу, чтобы элементы в списке отражали изменения. Я хочу, чтобы у них были значения, которые у них были до того, как я сделал мутации

Я не уверен, что понимаю комментарий. Позвольте мне набросать, что я имею в виду.

Предположим, у вас есть неизменный список:

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} узла.

Как только у вас есть неизменяемые данные, сохранение отмены повторов списков целых чисел становится точно таким же, как отмены повторов целых или строк или любой другой неизменный тип данных. Вы просто сохраняете состояние, не беспокоясь о том, что кто-то изменит это состояние.

...