ToList () - apakah ia membuat daftar baru?

176

Katakanlah saya punya kelas

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

Dan saya punya List<MyObject>, dan saya ToList()dan kemudian mengubah salah satu SimpleInt, akankah perubahan saya disebarkan kembali ke daftar asli. Dengan kata lain, apa yang akan menjadi hasil dari metode berikut?

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;

}

Mengapa?

P / S: Saya minta maaf jika tampaknya jelas untuk mencari tahu. Tapi saya tidak punya kompiler dengan saya sekarang ...

Graviton
sumber
Salah satu cara untuk mengungkapkannya adalah dengan .ToList()membuat salinan yang dangkal . Referensi disalin, tetapi referensi baru masih menunjuk ke contoh yang sama dengan referensi asli menunjuk. Ketika Anda memikirkan hal itu, ToListtidak dapat membuat new MyObject()saat MyObjectadalah classjenis.
Jeppe Stig Nielsen

Jawaban:

217

Ya, ToListakan membuat daftar baru, tetapi karena dalam hal ini MyObjectadalah jenis referensi maka daftar baru akan berisi referensi ke objek yang sama dengan daftar asli.

Memperbarui SimpleIntproperti objek yang dirujuk dalam daftar baru juga akan mempengaruhi objek yang setara dalam daftar asli.

(Jika MyObjectdideklarasikan sebagai structbukan dari classmaka daftar baru akan berisi salinan elemen-elemen dalam daftar asli, dan memperbarui properti elemen dalam daftar baru tidak akan mempengaruhi elemen setara dalam daftar asli.)

LukeH
sumber
1
Perhatikan juga bahwa dengan Liststruct, tugas seperti objectList[0].SimpleInt=5tidak akan diizinkan (kesalahan waktu kompilasi C #). Itu karena nilai balik getaccessor daftar pengindeks bukan variabel (itu adalah salinan yang dikembalikan dari nilai struct), dan oleh karena itu mengatur anggotanya .SimpleIntdengan ekspresi penugasan tidak diperbolehkan (itu akan bermutasi salinan yang tidak disimpan) . Nah, siapa yang menggunakan struct yang bisa berubah?
Jeppe Stig Nielsen
2
@Jeppe Stig Nielson: Ya. Dan, kompiler juga akan menghentikan Anda melakukan hal-hal seperti foreach (var s in listOfStructs) { s.SimpleInt = 42; }. The benar-benar Gotcha jahat adalah ketika Anda mencoba sesuatu seperti listOfStructs.ForEach(s => s.SimpleInt = 42): compiler memungkinkan dan berjalan kode tanpa pengecualian, tetapi struct dalam daftar akan tetap tidak berubah!
LukeH
62

Dari sumber Reflector'd:

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

Jadi ya, daftar asli Anda tidak akan diperbarui (yaitu penambahan atau penghapusan) namun objek yang dirujuk akan.

Chris S
sumber
32

ToList akan selalu membuat daftar baru, yang tidak akan mencerminkan perubahan berikutnya pada koleksi.

Namun, itu akan mencerminkan perubahan pada objek itu sendiri (Kecuali jika mereka bisa berubah struktur).

Dengan kata lain, jika Anda mengganti objek dalam daftar asli dengan objek yang berbeda, ToListmasih akan berisi objek pertama.
Namun, jika Anda memodifikasi salah satu objek dalam daftar asli, ToListmasih akan berisi objek yang sama (dimodifikasi).

Slaks
sumber
12

Jawaban yang diterima dengan benar menjawab pertanyaan OP berdasarkan pada contohnya. Namun, itu hanya berlaku saatToList diterapkan pada koleksi beton; itu tidak berlaku ketika elemen-elemen dari urutan sumber belum dipakai (karena eksekusi ditangguhkan). Dalam hal yang terakhir, Anda mungkin mendapatkan set item baru setiap kali Anda menelepon ToList(atau menyebutkan urutannya).

Berikut ini adalah adaptasi dari kode OP untuk menunjukkan perilaku ini:

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;
}

Sementara kode di atas mungkin tampak dibuat-buat, perilaku ini dapat muncul sebagai bug halus dalam skenario lain. Lihat contoh saya yang lain untuk situasi di mana ia menyebabkan tugas-tugas muncul berulang kali.

Douglas
sumber
11

Ya, itu membuat daftar baru. Ini dengan desain.

Daftar ini akan berisi hasil yang sama dengan urutan enumerable asli, tetapi terwujud menjadi koleksi persisten (dalam memori). Ini memungkinkan Anda untuk mengkonsumsi hasil beberapa kali tanpa mengeluarkan biaya mengkomputasi ulang urutan.

Keindahan dari urutan LINQ adalah bahwa mereka dapat dikomposasikan Seringkali, yang IEnumerable<T>Anda dapatkan adalah hasil dari menggabungkan beberapa penyaringan, pemesanan, dan / atau operasi proyeksi. Metode ekstensi menyukai ToList()dan ToArray()memungkinkan Anda untuk mengubah urutan yang dihitung menjadi koleksi standar.

LBushkin
sumber
6

Daftar baru dibuat tetapi item di dalamnya adalah referensi ke item asli (seperti dalam daftar asli). Perubahan pada daftar itu sendiri adalah independen, tetapi untuk item akan menemukan perubahan di kedua daftar.

Praha Sangha
sumber
6

Baru saja menemukan posting lama ini dan berpikir untuk menambahkan dua sen saya. Secara umum, jika saya ragu, saya dengan cepat menggunakan metode GetHashCode () pada objek apa pun untuk memeriksa identitas. Jadi untuk di atas -

    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();
    }

Dan jawab di mesin saya -

  • objs: 45653674
  • objs [0]: 41149443
  • benda: 45653674
  • benda [0]: 41149443
  • objectList: 39785641
  • objectList [0]: 41149443
  • whatInt: 5

Jadi pada dasarnya objek yang dibawanya tetap sama dalam kode di atas. Semoga pendekatan ini membantu.

vibhu
sumber
4

Saya pikir ini setara dengan menanyakan apakah ToList melakukan salinan yang dalam atau dangkal. Karena ToList tidak memiliki cara untuk mengkloning MyObject, ia harus melakukan salinan yang dangkal, sehingga daftar yang dibuat berisi referensi yang sama dengan yang asli, sehingga kode mengembalikan 5.

Timores
sumber
2

ToList akan membuat daftar baru.

Jika item dalam daftar adalah tipe nilai, mereka akan langsung diperbarui, jika mereka adalah tipe referensi, setiap perubahan akan tercermin kembali dalam objek yang direferensikan.

Oded
sumber
2

Dalam kasus di mana objek sumber adalah IEnumerable yang benar (yaitu bukan hanya kumpulan yang dikemas sebagai enumerable), ToList () mungkin TIDAK mengembalikan referensi objek yang sama seperti dalam IEnumerable asli. Ini akan mengembalikan daftar objek baru, tetapi objek-objek itu mungkin tidak sama atau bahkan sama dengan objek yang dihasilkan oleh IEnumerable ketika dienumerasi lagi

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

Ini akan memperbarui objek asli juga. Daftar baru akan berisi referensi ke objek yang terkandung di dalamnya, sama seperti daftar asli. Anda dapat mengubah elemen baik dan pembaruan akan tercermin di yang lain.

Sekarang jika Anda memperbarui daftar (menambah atau menghapus item) yang tidak akan tercermin dalam daftar lainnya.

kemiller2002
sumber
1

Saya tidak melihat di mana pun dalam dokumentasi bahwa ToList () selalu dijamin untuk mengembalikan daftar baru. Jika IEnumerable adalah Daftar, mungkin lebih efisien untuk memeriksanya dan mengembalikan Daftar yang sama.

Kekhawatirannya adalah bahwa kadang-kadang Anda mungkin ingin benar-benar yakin bahwa Daftar yang dikembalikan adalah! = Ke Daftar asli. Karena Microsoft tidak mendokumentasikan ToList akan mengembalikan Daftar baru, kami tidak dapat memastikan (kecuali seseorang menemukan dokumentasi itu). Itu juga bisa berubah di masa depan, bahkan jika itu berfungsi sekarang.

Daftar baru (enumerablestuff IEnumerable) dijamin untuk mengembalikan Daftar baru. Saya akan menggunakan ini sebagai gantinya.

Ben B.
sumber
2
Ia mengatakannya dalam dokumentasi di sini: " Metode ToList <TSource> (IEnumerable <TSource>) memaksa evaluasi kueri langsung dan mengembalikan Daftar <T> yang berisi hasil kueri. Anda dapat menambahkan metode ini ke kueri Anda untuk mendapatkan salinan hasil pencarian yang di-cache. "Kalimat kedua menegaskan bahwa perubahan apa pun pada daftar asli setelah kueri dievaluasi tidak berpengaruh pada daftar yang dikembalikan.
Raymond Chen
1
@ RaymondChen Ini berlaku untuk IEnumerables, tetapi tidak untuk Daftar. Meskipun ToListtampaknya membuat referensi objek daftar baru ketika dipanggil pada List, BenB benar ketika ia mengatakan bahwa ini tidak dijamin oleh dokumentasi MS.
Teejay
@RaymondChen Sesuai sumber ChrisS, IEnumerable<>.ToList()sebenarnya diimplementasikan new List<>(source)dan tidak ada penggantian khusus untuk List<>, jadi List<>.ToList()memang mengembalikan referensi objek daftar baru. Tetapi sekali lagi, sesuai dokumentasi MS, tidak ada jaminan untuk ini tidak berubah di masa depan, meskipun kemungkinan akan merusak sebagian besar kode di luar sana.
Teejay