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;

}

Чому?

П / С: Вибачте, якщо це здається очевидним. Але у мене зараз немає компілятора ...


Один із способів її фразування - це .ToList()створення дрібної копії. Посилання копіюються, але нові посилання все ще вказують на ті самі екземпляри, на які вказують оригінальні посилання. Коли ви думаєте про це, ToListне може створити жоден, new MyObject()коли MyObjectце classтип.
Jeppe Stig Nielsen

Відповіді:


217

Так, ToListстворимо новий список, але оскільки в даному випадку MyObjectце тип посилання, то новий список буде містити посилання на ті самі об’єкти, що і вихідний список.

Оновлення SimpleIntвластивості об'єкта, на який посилається новий список, також вплине на еквівалентний об'єкт у вихідному списку.

(Якщо він MyObjectбув оголошений structскоріше, ніж classтоді, то новий список міститиме копії елементів у вихідному списку, і оновлення властивості елемента в новому списку не вплине на еквівалентний елемент у вихідному списку.)


1
Також зауважте, що при Listструктурах таке призначення, як, наприклад objectList[0].SimpleInt=5, не буде дозволено (помилка компіляції в часі C #). Це тому, що повернене значення getаксесуара списку індексатора списку не є змінною (це повернута копія значення структури), а тому встановлення свого члена .SimpleIntіз виразом присвоєння заборонено (воно буде вимкнено копію, яка не зберігається) . Ну, хто все-таки використовує мутантні структури?
Jeppe Stig Nielsen

2
@Jeppe Stig Nielson: Так. І, аналогічно, компілятор також зупинить вас робити такі речі foreach (var s in listOfStructs) { s.SimpleInt = 42; }. Дійсно неприємний глюк, коли ви намагаєтеся що - щось на зразок listOfStructs.ForEach(s => s.SimpleInt = 42): компілятор дозволяє це і код працює без винятків, але в структурі списку залишаться без змін!
ЛукаХ

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все одно буде містити той самий (модифікований) об’єкт.


12

Прийнята відповідь правильно вирішує питання ОП на основі його прикладу. Однак він застосовується лише тоді, коли ToListйого застосовують до бетонної колекції; він не втримується, коли елементи послідовності джерела ще мають бути екземплярами (через відкладене виконання). У разі останнього ви можете отримувати новий набір елементів кожного разу, коли ви телефонуєте ToList(або перераховуєте послідовність).

Ось адаптація коду ОП для демонстрації такої поведінки:

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()дозволяють перетворити обчислену послідовність у стандартну колекцію.


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
  • список об'єктів: 39785641
  • objectList [0]: 41149443
  • щоІнт: 5

Таким чином, по суті об'єкт, який містить список, залишається тим самим у наведеному вище коді. Сподіваюся, що підхід допомагає.


4

Я думаю, що це рівнозначно запитуванню, чи ToList робить глибоку чи неглибоку копію. Оскільки ToList не має можливості клонувати MyObject, він повинен зробити дрібну копію, тому створений список містить ті ж посилання, що й оригінальний, тому код повертає 5.


2

ToList створить абсолютно новий список.

Якщо елементи зі списку є типовими значеннями, вони будуть безпосередньо оновлюватися, якщо вони є еталонними типами, будь-які зміни будуть відображені назад у посилаються об'єктах.


2

У випадку, коли вихідним об'єктом є справжній IEnumerable (тобто не просто колекція, упакована як перелічена), ToList () НЕ може повертати ті самі посилання на об'єкт, що і в оригінальному IEnumerable. Він поверне новий Перелік об'єктів, але ці об'єкти можуть бути не однаковими або навіть рівними об'єктам, отриманим IEnumerable, коли він перераховується знову


1
 var objectList = objects.ToList();
  objectList[0].SimpleInt=5;

Це також оновить вихідний об'єкт. Новий список буде містити посилання на об'єкти, що містяться в ньому, як і вихідний список. Ви можете змінити елементи або будь-яке, і оновлення відобразиться в іншому.

Тепер, якщо ви оновите список (додаючи або видаляючи елемент), який не буде відображено в іншому списку.


1

Я ніде не бачу в документації, що ToList () завжди гарантовано повертає новий список. Якщо IEnumerable є списком, може бути ефективніше перевірити це та просто повернути той самий Список.

Побоювання полягає в тому, що іноді ви можете бути абсолютно впевнені, що повернений список! = У вихідний список. Оскільки Microsoft не документує, що ToList поверне новий Список, ми не можемо бути впевнені (якщо хтось не знайде цю документацію). Це також може змінитися в майбутньому, навіть якщо це працює зараз.

новий Список (IEmumerable unumerablestuff) гарантовано поверне новий Перелік. Я б використав це замість цього.


2
Про це йдеться в документації тут: " Метод ToList <TSource> (IEnumerable <TSource>) примушує негайне оцінювання запитів і повертає список <T>, який містить результати запиту. Ви можете додати цей метод до запиту, щоб отримати кешована копія результатів запиту. "У другому реченні повторюється, що будь-які зміни оригінального списку після оцінки запиту не впливають на список, що повертається.
Реймонд Чен

1
@RaymondChen Це стосується IEnumerables, але не для списків. Хоча, ToListздається, створюється нова посилання на об'єкт списку, коли він викликається List, БенБ має рацію, коли каже, що це не гарантується документацією на MS.
Teejay

@RaymondChen Відповідно до джерела ChrisS, IEnumerable<>.ToList()реально реалізований як new List<>(source)і немає конкретного переопрацювання List<>, тому List<>.ToList()дійсно повертає нову посилання на об'єкт списку. Але ще раз, згідно з документацією на MS, немає гарантії, що це не зміниться в майбутньому, хоча це, ймовірно, порушить більшість коду там.
Teejay
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.