Как изменить свой новый список без изменения исходного списка?

У меня есть список, который заполняется некоторыми данными из операции, и я храню его в кеше памяти. Теперь я хочу другой список, который содержит некоторые вспомогательные данные из списка на основе некоторого условия.

Как видно из приведенного ниже кода, я выполняю некоторую операцию в целевом списке. Проблема в том, что любые изменения, которые я делаю в целевом списке, также выполняются в mainList. Я думаю, что его из-за ссылки то же самое или что-то.

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

List<Item> target = mainList;
SomeOperationFunction(target);

 void List<Item> SomeOperationFunction(List<Item> target)
{
  target.removeat(3);
  return target;
}

Ответ 1

Вам нужно клонировать ваш список в вашем методе, потому что List<T> - это класс, поэтому он ссылочный тип и передается по ссылке.

Например:

List<Item> SomeOperationFunction(List<Item> target)
{
  List<Item> tmp = target.ToList();
  tmp.RemoveAt(3);
  return tmp;
}

или

List<Item> SomeOperationFunction(List<Item> target)
{
  List<Item> tmp = new List<Item>(target);
  tmp.RemoveAt(3);
  return tmp;
}

или

List<Item> SomeOperationFunction(List<Item> target)
{
  List<Item> tmp = new List<Item>();
  tmp.AddRange(target);
  tmp.RemoveAt(3);
  return tmp;
}

Ответ 2

Вам нужно сделать копию списка, чтобы изменения в копии не повлияли на оригинал. Самый простой способ сделать это - использовать метод расширения ToList в System.Linq.

var newList = SomeOperationFunction(target.ToList());

Ответ 3

Сначала создайте новый список и оперируйте его, потому что List является ссылочным типом, т.е. когда вы передаете его в функции, вы не просто передаете значение, а сам фактический объект.

Если вы просто назначаете target на mainList, обе переменные указывают на один и тот же объект, поэтому вам нужно создать новый список:

List<Item> target = new List<Item>(mainList);

void List<Item> SomeOperationFunction() не имеет смысла, потому что либо вы ничего не возвращаете (void), либо вы возвращаете List<T>. Поэтому либо удалите оператор return из вашего метода, либо верните новый List<Item>. В последнем случае я бы переписал это как:

List<Item> target = SomeOperationFunction(mainList);

List<Item> SomeOperationFunction(List<Item> target)
{
    var newList = new List<Item>(target);
    newList.RemoveAt(3);
    return newList;
}

Ответ 4

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

Чтобы этого не сделать, вам нужно будет создать новый список в методе, скопировать его содержимое target и затем выполнить удаление в операции в новом списке.

О ссылках и типах значений

Ответ 5

Вы видите, что исходный список изменяется, потому что по умолчанию любые не примитивные объекты передаются по ссылке (это фактически передается по значению, значение является ссылкой, но это другое дело).

Что вам нужно сделать, это клонировать объект. Этот вопрос поможет вам с некоторым кодом клонировать список в С#: Как клонировать общий список на С#?

Ответ 6

Так как a List является ссылочным типом, то, что передается функции, является ссылкой на исходный список.

См. статью MSDN для получения дополнительной информации о том, как параметры передаются на С#.

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

void List<Item> SomeOperationFunction(List<Item> target)
{
  var newList = new List<Item>(target);
  newList.RemoveAt(3);
  return newList; // return copy of list
}

Как указывал Оливье Жако-Дескомб в комментариях к другому ответу, важно иметь в виду, что

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

Ответ 7

Вместо того, чтобы назначать mainList для таргетинга, я бы сделал: target.AddRange(mainList);

Затем у вас будет копия элементов вместо ссылки на список.

Ответ 8

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

List<Item> target = mainList; Должно быть List<item> target = new List<Item>(mainList);

Ответ 9

Вам нужно будет сделать копию списка, так как в исходном коде то, что вы делаете, просто проходит, как вы правильно подозревали, ссылку (кто-то назовет ее указателем).

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

List<Item> SomeOperationFunction(List<Item> target)
{
    List<Item> result = new List<Item>(target);
    result.removeat(3);
    return result;
}

Или создайте MemberWiseClone:

List<Item> SomeOperationFunction(List<Item> target)
{
    List<Item> result = target.MemberWiseClone();
    result.removeat(3);
    return result;
}

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

List<Item> target = SomeOperationFunction(mainList);

Примечание: элементы списка не будут скопированы (копируется только их ссылка), поэтому изменение внутреннего состояния элементов повлияет на оба списка.

Ответ 10

Даже если вы создаете новый список, ссылки на элементы в новом списке будут по-прежнему указывать на элементы в старом списке, поэтому мне нравится использовать этот метод расширения, если мне нужен новый список с новыми ссылками..

public static IEnumerable<T> Clone<T>(this IEnumerable<T> target) where T : ICloneable
{
    If (target.IsNull())
        throw new ArgumentException();

    List<T> retVal = new List<T>();

    foreach (T currentItem in target)
        retVal.Add((T)(currentItem.Clone()));

    return retVal.AsEnumerable();
}