ToList () - это создает новый список?

176

Допустим, у меня есть класс

public class MyObject
{
   public int SimpleInt{get;set;}
}

И у меня есть List<MyObject>, и я ToList()это, а затем изменить один из SimpleInt, будут ли мои изменения будут распространены обратно в первоначальный список. Другими словами, каков будет результат следующего метода?

public void RunChangeList()
{
  var objs = new List<MyObject>(){new MyObject(){SimpleInt=0}};
  var whatInt = ChangeToList(objs );
}
public int ChangeToList(List<MyObject> objects)
{
  var objectList = objects.ToList();
  objectList[0].SimpleInt=5;
  return objects[0].SimpleInt;

}

Зачем?

P / S: Извините, если кажется очевидным выяснить. Но сейчас у меня нет компилятора ...

Гравитон
источник
Один из способов сформулировать это - .ToList()сделать мелкую копию. Ссылки копируются, но новые ссылки по-прежнему указывают на те же экземпляры, на которые указывают исходные ссылки. Когда вы думаете об этом, ToListне можете создать что-либо, new MyObject()когда MyObjectесть classтип.
Джепп Стиг Нильсен

Ответы:

217

Да, ToListсоздаст новый список, но поскольку в этом случае MyObjectэто ссылочный тип, то новый список будет содержать ссылки на те же объекты, что и исходный список.

Обновление SimpleIntсвойства объекта, на который есть ссылка в новом списке, также повлияет на эквивалентный объект в исходном списке.

(Если MyObjectбыло объявлено, structа не как, classто новый список будет содержать копии элементов в исходном списке, и обновление свойства элемента в новом списке не повлияет на эквивалентный элемент в исходном списке.)

LukeH
источник
1
Также обратите внимание, что при использовании Listструктур структура присваивания, как objectList[0].SimpleInt=5, не допускается (ошибка компиляции C #). Это связано с тем, что возвращаемое значение метода getдоступа индексатора списка не является переменной (это возвращаемая копия значения структуры), и поэтому установка его члена .SimpleIntс помощью выражения присваивания недопустима (это приведет к изменению копии, которая не сохраняется). , Ну, кто использует изменяемые структуры в любом случае?
Джеппе Стиг Нильсен
2
@Джеп Стиг Нильсон: Да. И, аналогично, компилятор также не позволит вам делать такие вещи, как foreach (var s in listOfStructs) { s.SimpleInt = 42; }. Действительно неприятный глюк, когда вы пытаетесь что - то вроде listOfStructs.ForEach(s => s.SimpleInt = 42): компилятор позволяет это и код работает без исключений, но в Структурах списка останутся без изменений!
ЛукиH
62

Из источника Reflector'd:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return new List<TSource>(source);
}

Так что да, ваш первоначальный список не будет обновляться (то есть, добавления или удаления), как и ссылочные объекты.

Крис С
источник
32

ToList всегда будет создавать новый список, который не будет отражать любые последующие изменения в коллекции.

Тем не менее, он будет отражать изменения в самих объектах (если они не изменяемые структуры).

Другими словами, если вы замените объект в исходном списке другим объектом, ToListон все равно будет содержать первый объект.
Однако если вы измените один из объектов в исходном списке, ToListон все равно будет содержать тот же (измененный) объект.

SLaks
источник
12

Принятый ответ правильно отвечает на вопрос ОП на основании его примера. Тем не менее, он применяется только тогда, когда ToListприменяется к конкретной коллекции; он не сохраняется, когда элементы исходной последовательности еще не созданы (из-за отложенного выполнения). В последнем случае вы можете получать новый набор предметов при каждом вызове ToList(или перечислять последовательность).

Вот адаптация кода OP, чтобы продемонстрировать это поведение:

public static void RunChangeList()
{
    var objs = Enumerable.Range(0, 10).Select(_ => new MyObject() { SimpleInt = 0 });
    var whatInt = ChangeToList(objs);   // whatInt gets 0
}

public static int ChangeToList(IEnumerable<MyObject> objects)
{
    var objectList = objects.ToList();
    objectList.First().SimpleInt = 5;
    return objects.First().SimpleInt;
}

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

Дуглас
источник
11

Да, это создает новый список. Это по замыслу.

Список будет содержать те же результаты, что и исходная перечисляемая последовательность, но материализован в постоянную (в памяти) коллекцию. Это позволяет использовать результаты несколько раз без затрат на повторный расчет последовательности.

Прелесть LINQ-последовательностей в том, что они компонуются. Часто IEnumerable<T>вы получаете результат объединения нескольких операций фильтрации, упорядочения и / или проецирования. Методы расширения, такие как ToList()и, ToArray()позволяют преобразовывать вычисленную последовательность в стандартную коллекцию.

LBushkin
источник
6

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

Прит Сангха
источник
6

Просто наткнулся на этот старый пост и подумал добавить два моих цента. Обычно, если у меня есть сомнения, я быстро использую метод GetHashCode () для любого объекта, чтобы проверить идентичность. Так что для выше -

    public class MyObject
{
    public int SimpleInt { get; set; }
}


class Program
{

    public static void RunChangeList()
    {
        var objs = new List<MyObject>() { new MyObject() { SimpleInt = 0 } };
        Console.WriteLine("objs: {0}", objs.GetHashCode());
        Console.WriteLine("objs[0]: {0}", objs[0].GetHashCode());
        var whatInt = ChangeToList(objs);
        Console.WriteLine("whatInt: {0}", whatInt.GetHashCode());
    }

    public static int ChangeToList(List<MyObject> objects)
    {
        Console.WriteLine("objects: {0}", objects.GetHashCode());
        Console.WriteLine("objects[0]: {0}", objects[0].GetHashCode());
        var objectList = objects.ToList();
        Console.WriteLine("objectList: {0}", objectList.GetHashCode());
        Console.WriteLine("objectList[0]: {0}", objectList[0].GetHashCode());
        objectList[0].SimpleInt = 5;
        return objects[0].SimpleInt;

    }

    private static void Main(string[] args)
    {
        RunChangeList();
        Console.ReadLine();
    }

И ответь на моей машине -

  • objs: 45653674
  • objs [0]: 41149443
  • объекты: 45653674
  • объекты [0]: 41149443
  • objectList: 39785641
  • objectList [0]: 41149443
  • Что: 5

Таким образом, по существу, объект, который несет список, остается таким же в приведенном выше коде Надеюсь, что подход поможет.

вибху
источник
4

Я думаю, что это равносильно тому, чтобы спросить, делает ли ToList глубокую или поверхностную копию. Поскольку ToList не имеет возможности клонировать MyObject, он должен делать поверхностную копию, поэтому созданный список содержит те же ссылки, что и исходный, поэтому код возвращает 5.

Timores
источник
2

ToList создаст новый список.

Если элементы в списке являются типами значений, они будут непосредственно обновлены, если они являются ссылочными типами, любые изменения будут отражены обратно в ссылочных объектах.

Одед
источник
2

В случае, когда исходный объект является истинным IEnumerable (т. Е. Не просто коллекцией, упакованной как перечисляемая), ToList () НЕ может возвращать те же ссылки на объекты, что и в исходном IEnumerable. Он вернет новый список объектов, но эти объекты могут не совпадать или даже не совпадать с объектами, полученными IEnumerable при его повторном перечислении

prmph
источник
1
 var objectList = objects.ToList();
  objectList[0].SimpleInt=5;

Это также обновит исходный объект. Новый список будет содержать ссылки на объекты, содержащиеся в нем, так же, как исходный список. Вы можете изменить элементы либо, и обновление будет отражено в другом.

Теперь, если вы обновите список (добавление или удаление элемента), который не будет отражен в другом списке.

kemiller2002
источник
1

Я не вижу в документации нигде, чтобы ToList () всегда гарантированно возвращал новый список. Если IEnumerable является списком, может быть более эффективно проверить это и просто вернуть тот же список.

Беспокойство заключается в том, что иногда вам может потребоваться быть абсолютно уверенным, что возвращенный список соответствует исходному списку! Поскольку Microsoft не документирует, что ToList вернет новый список, мы не можем быть уверены (если кто-то не найдет эту документацию). Это может также измениться в будущем, даже если это работает сейчас.

new List (IEnumerable enumerablestuff) гарантированно возвращает новый список. Я бы использовал это вместо этого.

Бен Б.
источник
2
Это сказано в документации здесь: « Метод ToList <TSource> (IEnumerable <TSource>) вызывает немедленную оценку запроса и возвращает List <T>, содержащий результаты запроса. Вы можете добавить этот метод к своему запросу, чтобы получить кэшированная копия результатов запроса ». Второе предложение повторяет, что любые изменения в исходном списке после оценки запроса не влияют на возвращаемый список.
Раймонд Чен
1
@RaymondChen Это верно для IEnumerables, но не для списков. Хотя ToListкажется, что он создает новую ссылку на объект списка при вызове List, BenB прав, когда говорит, что это не гарантируется документацией MS.
Teejay
@RaymondChen Согласно источнику ChrisS, IEnumerable<>.ToList()фактически реализован как, new List<>(source)и для него нет специального переопределения List<>, поэтому List<>.ToList()действительно возвращает новую ссылку на объект списка. Но, опять же, в соответствии с документацией MS, нет никаких гарантий, что это не изменится в будущем, хотя это, скорее всего, повредит большую часть кода.
Teejay