IEnumerable vs List - Apa yang Digunakan? Bagaimana mereka bekerja?

678

Saya memiliki keraguan tentang bagaimana Enumerator bekerja, dan LINQ. Pertimbangkan dua pilihan sederhana ini:

List<Animal> sel = (from animal in Animals 
                    join race in Species
                    on animal.SpeciesKey equals race.SpeciesKey
                    select animal).Distinct().ToList();

atau

IEnumerable<Animal> sel = (from animal in Animals 
                           join race in Species
                           on animal.SpeciesKey equals race.SpeciesKey
                           select animal).Distinct();

Saya mengubah nama-nama objek asli saya sehingga ini terlihat seperti contoh yang lebih umum. Permintaan itu sendiri tidak begitu penting. Yang ingin saya tanyakan adalah ini:

foreach (Animal animal in sel) { /*do stuff*/ }
  1. Saya perhatikan bahwa jika saya menggunakan IEnumerable, ketika saya men-debug dan memeriksa "sel", yang dalam hal ini adalah IEnumerable, ia memiliki beberapa anggota yang menarik: "dalam", "luar", "innerKeySelector" dan "outerKeySelector", 2 terakhir ini muncul menjadi delegasi. Anggota "batin" tidak memiliki instance "Hewan" di dalamnya, melainkan instance "Spesies", yang sangat aneh bagi saya. Anggota "luar" memang mengandung instance "Hewan". Saya berasumsi bahwa kedua delegasi menentukan mana yang masuk dan apa yang keluar dari sana?

  2. Saya perhatikan bahwa jika saya menggunakan "Distinct", "inner" berisi 6 item (ini tidak benar karena hanya 2 yang Distinct), tetapi "outer" memang mengandung nilai yang benar. Sekali lagi, mungkin metode yang didelegasikan menentukan ini, tetapi ini sedikit lebih dari yang saya tahu tentang IEnumerable.

  3. Yang paling penting, yang mana dari dua opsi ini yang terbaik untuk kinerja?

Konversi daftar jahat melalui .ToList()?

Atau mungkin menggunakan enumerator secara langsung?

Jika Anda bisa, tolong jelaskan juga sedikit atau lempar beberapa tautan yang menjelaskan penggunaan IEnumerable ini.

Axonn
sumber

Jawaban:

741

IEnumerablemenggambarkan perilaku, sedangkan Daftar adalah implementasi dari perilaku itu. Ketika Anda menggunakan IEnumerable, Anda memberi kompiler kesempatan untuk menunda pekerjaan sampai nanti, mungkin mengoptimalkan sepanjang jalan. Jika Anda menggunakan ToList (), Anda memaksa kompiler untuk segera memverifikasi hasilnya.

Setiap kali saya "menumpuk" ekspresi LINQ, saya menggunakan IEnumerable, karena dengan hanya menentukan perilaku saya memberi LINQ kesempatan untuk menunda evaluasi dan mungkin mengoptimalkan program. Ingat bagaimana LINQ tidak menghasilkan SQL untuk query database sampai Anda menghitungnya? Pertimbangkan ini:

public IEnumerable<Animals> AllSpotted()
{
    return from a in Zoo.Animals
           where a.coat.HasSpots == true
           select a;
}

public IEnumerable<Animals> Feline(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Felidae"
           select a;
}

public IEnumerable<Animals> Canine(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Canidae"
           select a;
}

Sekarang Anda memiliki metode yang memilih sampel awal ("AllSpotted"), ditambah beberapa filter. Jadi sekarang Anda bisa melakukan ini:

var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());

Jadi, apakah lebih cepat menggunakan Daftar IEnumerable? Hanya jika Anda ingin mencegah permintaan dieksekusi lebih dari satu kali. Tapi apakah ini lebih baik secara keseluruhan? Nah di atas, Macan Tutul dan Hyena dapat dikonversi menjadi query SQL tunggal masing-masing , dan database hanya mengembalikan baris yang relevan. Tetapi jika kami telah mengembalikan daftar dari AllSpotted(), maka itu mungkin berjalan lebih lambat karena database dapat mengembalikan data jauh lebih banyak dari yang sebenarnya dibutuhkan, dan kami membuang siklus melakukan penyaringan di klien.

Dalam sebuah program, mungkin lebih baik menunda konversi kueri Anda ke daftar sampai akhir, jadi jika saya akan menghitung melalui Leopards dan Hyenas lebih dari sekali, saya akan melakukan ini:

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();
Chris Wenham
sumber
11
Saya pikir mereka merujuk pada dua sisi bergabung. Jika Anda melakukan "SELECT * FROM Animals JOIN Species ..." maka bagian dalam gabungan adalah Hewan, dan bagian luar adalah Spesies.
Chris Wenham
10
Ketika saya sudah membaca jawaban tentang: IEnumerable <T> vs IQueryable <T> Saya melihat penjelasan analog, sehingga IEnumerable secara otomatis memaksa runtime untuk menggunakan LINQ ke Objects untuk menanyakan koleksi. Jadi saya bingung antara 3 tipe ini. stackoverflow.com/questions/2876616/…
Bronek
4
@Bronek Jawaban yang Anda tautkan adalah benar. IEnumerable<T>akan menjadi LINQ-To-Objects setelah bagian pertama yang berarti semua yang terlihat harus dikembalikan untuk menjalankan Feline. Di sisi lain, IQuertable<T>akan memungkinkan kueri disempurnakan, hanya menarik Spines Felines yang turun.
Nate
21
Jawaban ini sangat menyesatkan! Komentar @ Nate menjelaskan alasannya. Jika Anda menggunakan IEnumerable <T>, filter akan terjadi di sisi klien apa pun yang terjadi.
Hans
5
Ya AllSpotted () akan dijalankan dua kali. Masalah yang lebih besar dengan jawaban ini adalah pernyataan berikut: "Nah di atas, Macan Tutul dan Hyena bisa dikonversi menjadi query SQL tunggal masing-masing, dan database hanya mengembalikan baris yang relevan." Ini salah, karena klausa mana dipanggil pada IEnumerable <> dan yang hanya tahu bagaimana untuk loop melalui objek yang sudah datang dari database. Jika Anda membuat kembalinya AllSpotted () dan parameter Feline () dan Canine () ke IQueryable, maka filter akan terjadi dalam SQL dan jawaban ini masuk akal.
Hans
178

Ada artikel yang sangat bagus yang ditulis oleh: Claudio Bernasconi's TechBlog di sini: Kapan menggunakan IEnumerable, ICollection, IList and List

Di sini beberapa dasar menunjukkan tentang skenario dan fungsi:

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

rubStackOverflow
sumber
25
Seharusnya ditunjukkan bahwa artikel ini hanya untuk publik yang menghadapi bagian kode Anda, bukan pekerjaan internal. Listmerupakan implementasi dari IListdan dengan demikian memiliki fungsi tambahan di atas mereka yang IList(misalnya Sort, Find, InsertRange). Jika Anda memaksakan diri untuk menggunakan IListlebih List, Anda kehilangan metode ini yang mungkin Anda perlukan
Jonathan Twite
4
Jangan lupaIReadOnlyCollection<T>
Dandré
2
Mungkin juga bermanfaat untuk menyertakan array sederhana di []sini.
jbyrd
Meskipun mungkin disukai, terima kasih telah berbagi grafik dan artikel ini
Daniel
134

Kelas yang mengimplementasikan IEnumerablememungkinkan Anda untuk menggunakan foreachsintaks.

Pada dasarnya ia memiliki metode untuk mendapatkan item berikutnya dalam koleksi. Tidak perlu seluruh koleksi berada di memori dan tidak tahu berapa banyak item di dalamnya, foreachterus dapatkan item berikutnya sampai habis.

Ini bisa sangat berguna dalam keadaan tertentu, misalnya dalam tabel database besar Anda tidak ingin menyalin semuanya ke dalam memori sebelum Anda mulai memproses baris.

Sekarang Listmengimplementasikan IEnumerable, tetapi mewakili seluruh koleksi dalam memori. Jika Anda memiliki IEnumerabledan menelepon, .ToList()Anda membuat daftar baru dengan isi enumerasi dalam memori.

Ekspresi LINQ Anda mengembalikan enumerasi, dan secara default ekspresi dieksekusi ketika Anda beralih menggunakan foreach. Sebuah IEnumerablemengeksekusi pernyataan LINQ ketika Anda iterate foreach, tetapi Anda dapat memaksa untuk iterate lebih cepat menggunakan .ToList().

Inilah yang saya maksud:

var things = 
    from item in BigDatabaseCall()
    where ....
    select item;

// this will iterate through the entire linq statement:
int count = things.Count();

// this will stop after iterating the first one, but will execute the linq again
bool hasAnyRecs = things.Any();

// this will execute the linq statement *again*
foreach( var thing in things ) ...

// this will copy the results to a list in memory
var list = things.ToList()

// this won't iterate through again, the list knows how many items are in it
int count2 = list.Count();

// this won't execute the linq statement - we have it copied to the list
foreach( var thing in list ) ...
Keith
sumber
2
Tetapi apa yang terjadi jika Anda menjalankan foreach pada IEnumerable tanpa mengubahnya menjadi Daftar terlebih dahulu ? Apakah ini membawa seluruh koleksi dalam memori? Atau, apakah itu instantiate elemen satu per satu, karena iterates di atas foreach loop? terima kasih
Pap
@ Pap yang terakhir: dieksekusi lagi, tidak ada yang secara otomatis di-cache dalam memori.
Keith
Sepertinya perbedaan kuncinya adalah 1) semuanya dalam memori atau tidak. 2) IEnumerable izinkan saya menggunakan foreachsementara Daftar akan pergi dengan mengatakan indeks. Sekarang, jika saya ingin tahu jumlah / panjang dari thingsebelumnya, IEnumerable tidak akan membantu, kan?
Jeb50
@ Jeb50 Tidak persis - keduanya Listdan Arrayimplementasikan IEnumerable. Anda dapat menganggapnya IEnumerablesebagai penyebut umum terendah yang berfungsi baik dalam koleksi memori maupun yang besar yang mendapatkan satu item sekaligus. Ketika Anda menelepon, IEnumerable.Count()Anda mungkin menelepon .Lengthproperti cepat atau melalui seluruh koleksi - intinya adalah bahwa IEnumerableAnda tidak tahu. Itu bisa menjadi masalah, tetapi jika Anda hanya akan melakukannya foreachmaka Anda tidak peduli - kode Anda akan bekerja dengan Arrayatau DataReadersama.
Keith
1
@MFouadKajj Saya tidak tahu tumpukan apa yang Anda gunakan, tapi hampir pasti tidak membuat permintaan dengan setiap baris. Server menjalankan kueri dan menghitung titik awal dari set hasil, tetapi tidak mendapatkan semuanya. Untuk set hasil kecil, ini sepertinya hanya satu perjalanan, untuk yang besar Anda mengirim permintaan untuk lebih banyak baris dari hasil, tetapi itu tidak menjalankan kembali seluruh kueri.
Keith
97

Tidak ada yang menyebutkan satu perbedaan penting, ironisnya menjawab pertanyaan yang ditutup sebagai duplikat dari ini.

IEnumerable adalah read-only dan List tidak.

Lihat Perbedaan praktis antara Daftar dan IEnumerable

CAD cowok
sumber
Sebagai tindak lanjut, apakah itu karena aspek Antarmuka atau karena aspek Daftar? yaitu apakah IList juga dapat dibaca?
Jason Masters
IList bukan hanya baca - docs.microsoft.com/en-us/dotnet/api/... IEnumerable adalah read-only karena tidak memiliki metode untuk menambah atau menghapus apa pun setelah dibangun, ini adalah salah satu antarmuka dasar yang IList meluas (lihat tautan)
CAD bloke
67

Hal yang paling penting untuk disadari adalah bahwa, menggunakan Linq, permintaan tidak segera dievaluasi. Hal ini hanya dijalankan sebagai bagian dari iterasi melalui dihasilkan IEnumerable<T>dalam foreach- yang ini apa semua delegasi aneh lakukan.

Jadi, contoh pertama mengevaluasi permintaan segera dengan memanggil ToListdan memasukkan hasil permintaan dalam daftar.
Contoh kedua mengembalikan sebuah IEnumerable<T>yang berisi semua informasi yang diperlukan untuk menjalankan kueri nanti.

Dalam hal kinerja, jawabannya tergantung . Jika Anda ingin hasil dievaluasi sekaligus (misalnya, Anda mengubah struktur yang Anda tanyakan nanti, atau jika Anda tidak ingin iterasi lebih IEnumerable<T>lama dari itu) gunakan daftar. Lain gunakan IEnumerable<T>. Defaultnya adalah menggunakan evaluasi sesuai permintaan dalam contoh kedua, karena yang umumnya menggunakan lebih sedikit memori, kecuali ada alasan khusus untuk menyimpan hasil dalam daftar.

thecoop
sumber
Hai dan terima kasih telah menjawab :: -). Ini menjernihkan hampir semua keraguan saya. Adakah ide mengapa Enumerable "terpecah" menjadi "dalam" dan "luar"? Ini terjadi ketika saya memeriksa elemen dalam mode debug / break via mouse. Apakah ini mungkin kontribusi Visual Studio? Menghitung di tempat dan menunjukkan input dan output dari Enum?
Axonn
5
Itulah yang Joinmelakukan itu bekerja - dalam dan luar adalah dua sisi bergabung. Secara umum, jangan khawatir tentang apa yang sebenarnya ada di IEnumerablesdalamnya, karena akan sangat berbeda dari kode Anda yang sebenarnya. Hanya khawatir tentang output aktual ketika Anda mengulanginya :)
thecoop
40

Keuntungan dari IEnumerable adalah eksekusi yang ditangguhkan (biasanya dengan database). Permintaan tidak akan dieksekusi sampai Anda benar-benar mengulang data. Ini permintaan menunggu sampai dibutuhkan (alias pemuatan malas).

Jika Anda memanggil ToList, kueri akan dieksekusi, atau "terwujud" seperti yang ingin saya katakan.

Ada pro dan kontra untuk keduanya. Jika Anda memanggil ToList, Anda dapat menghapus beberapa misteri ketika kueri dieksekusi. Jika Anda tetap menggunakan IEnumerable, Anda mendapatkan keuntungan bahwa program tidak melakukan pekerjaan apa pun sampai itu benar-benar diperlukan.

Matt Sherman
sumber
25

Saya akan membagikan satu konsep yang disalahgunakan yang saya masukkan ke dalam satu hari:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));


// updating existing list
names[0] = "ford";

// Guess what should be printed before continuing
print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

Hasil yang diharapkan

// I was expecting    
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari

Hasil yang sebenarnya

// what printed actualy   
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari

Penjelasan

Sebagai jawaban lain, evaluasi hasil ditunda sampai memanggil ToListatau metode doa serupa misalnya ToArray.

Jadi saya dapat menulis ulang kode dalam hal ini sebagai:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

// updating existing list
names[0] = "ford";

// before calling ToList directly
var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));

print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

Mainkan di sekeliling

https://repl.it/E8Ki/0

amd
sumber
1
Itu karena metode LINQ (ekstensi) yang dalam hal ini berasal dari IEnumerable di mana hanya membuat kueri tetapi tidak mengeksekusinya (di belakang layar pohon ekspresi digunakan). Dengan cara ini Anda memiliki kemungkinan untuk melakukan banyak hal dengan permintaan tersebut tanpa menyentuh data (dalam hal ini data dalam daftar). Metode daftar mengambil kueri yang disiapkan dan mengeksekusinya terhadap sumber data.
Bronek
2
Sebenarnya, saya membaca semua jawaban, dan jawaban Anda adalah yang saya pilih, karena jelas menyatakan perbedaan antara keduanya tanpa secara khusus berbicara tentang LINQ / SQL. Sangat penting untuk mengetahui semua ini SEBELUM Anda mendapatkan LINQ / SQL. Mengagumi.
BeemerGuy
Itu adalah perbedaan penting untuk dijelaskan tetapi "hasil yang diharapkan" tidak benar-benar diharapkan. Anda mengatakan itu semacam gotcha daripada desain.
Neme
@Neme, ya Itu harapan saya sebelum saya mengerti cara IEnumerablekerjanya, tapi sekarang Bukankah lebih karena saya tahu;)
amd
15

Jika semua yang ingin Anda lakukan adalah menghitungnya, gunakan IEnumerable.

Namun berhati-hatilah bahwa mengubah koleksi asli yang disebutkan adalah operasi yang berbahaya - dalam hal ini, Anda ingin melakukannya ToListterlebih dahulu. Ini akan membuat elemen daftar baru untuk setiap elemen dalam memori, menghitung IEnumerabledan dengan demikian kurang berkinerja jika Anda hanya menghitung satu kali - tetapi lebih aman dan kadang-kadang Listmetode yang berguna (misalnya dalam akses acak).

Daren Thomas
sumber
1
Saya tidak yakin mengatakan bahwa membuat daftar berarti kinerja yang lebih rendah.
Steven Sudit
@ Steven: memang seperti kata thecoop dan Chris, kadang-kadang mungkin perlu menggunakan Daftar. Dalam kasus saya, saya menyimpulkan itu bukan. @ Daren: apa yang Anda maksud dengan "ini akan membuat daftar baru untuk setiap elemen dalam memori"? Mungkin Anda berarti "entri daftar"? :: -).
Axonn
@ Ya, saya memasukkan entri. tetap.
Daren Thomas
@ Sebelas Jika Anda berencana untuk mengulangi elemen-elemen dalam IEnumerable, kemudian membuat daftar terlebih dahulu (dan mengulangi itu) berarti Anda mengulangi elemen dua kali . Jadi kecuali Anda ingin melakukan operasi yang lebih efisien dalam daftar, ini benar-benar berarti kinerja yang lebih rendah.
Daren Thomas
3
@jerhewet: tidak pernah merupakan ide yang baik untuk mengubah urutan yang diulangi. Hal-hal buruk akan terjadi. Abstraksi akan bocor. Setan akan masuk ke dimensi kita dan mendatangkan malapetaka. Jadi ya, .ToList()bantu di sini;)
Daren Thomas
5

Selain semua jawaban yang diposting di atas, berikut adalah dua sen saya. Ada banyak tipe lain selain List yang mengimplementasikan IEnumerable seperti ICollection, ArrayList, dll. Jadi jika kita memiliki IEnumerable sebagai parameter dari metode apa pun, kita bisa meneruskan semua tipe koleksi ke fungsi. Yaitu kita dapat memiliki metode untuk beroperasi pada abstraksi bukan implementasi spesifik.

Ananth
sumber
1

Ada banyak kasus (seperti daftar tak terbatas atau daftar yang sangat besar) di mana IEnumerable tidak dapat diubah menjadi Daftar. Contoh yang paling jelas adalah semua bilangan prima, semua pengguna facebook dengan detailnya, atau semua item di ebay.

Perbedaannya adalah bahwa "Daftar" objek disimpan "di sini dan sekarang", sedangkan "IEnumerable" objek bekerja "hanya satu per satu". Jadi, jika saya memeriksa semua item di ebay, satu per satu akan menjadi sesuatu yang bahkan dapat ditangani oleh komputer kecil, tetapi ".ToList ()" pasti akan membuat saya kehabisan memori, tidak peduli seberapa besar komputer saya. Tidak ada komputer yang dapat dengan sendirinya memuat dan menangani data dalam jumlah sangat besar.

[Sunting] - Tidak perlu dikatakan - itu bukan "ini atau itu". seringkali masuk akal untuk menggunakan daftar dan IEnumerable di kelas yang sama. Tidak ada komputer di dunia yang dapat mendaftar semua bilangan prima, karena menurut definisi ini akan membutuhkan jumlah memori yang tak terbatas. Tetapi Anda dapat dengan mudah memikirkan class PrimeContaineryang berisi IEnumerable<long> primes, yang karena alasan yang jelas juga mengandung SortedList<long> _primes. semua bilangan prima dihitung sejauh ini. perdana berikutnya yang akan diperiksa hanya akan dijalankan terhadap bilangan prima yang ada (hingga akar kuadrat). Dengan begitu Anda memperoleh keduanya - bilangan prima satu per satu (IEnumerable) dan daftar "bilangan prima sejauh ini" yang bagus, yang merupakan perkiraan yang cukup bagus dari seluruh daftar (tak terbatas).

LongChalk
sumber