Kapan menggunakan IList dan kapan menggunakan Daftar

180

Saya tahu bahwa IList adalah antarmuka dan Daftar adalah tipe konkret tapi saya masih tidak tahu kapan harus menggunakan masing-masing. Apa yang saya lakukan sekarang adalah jika saya tidak memerlukan metode Sort atau FindAll saya menggunakan antarmuka. Apakah saya benar? Apakah ada cara yang lebih baik untuk memutuskan kapan akan menggunakan antarmuka atau tipe konkret?

Rismo
sumber
1
Jika ada yang masih bertanya-tanya, saya menemukan jawaban terbaik di sini: stackoverflow.com/questions/400135/listt-or-ilistt
Crismogram

Jawaban:

175

Ada dua aturan yang saya ikuti:

  • Terima tipe paling dasar yang akan berhasil
  • Kembalikan jenis terkaya yang dibutuhkan pengguna Anda

Jadi ketika menulis fungsi atau metode yang mengambil koleksi, tulislah itu bukan untuk mengambil Daftar, tetapi sebuah IList <T>, sebuah ICollection <T>, atau IEnumerable <T>. Antarmuka generik masih akan bekerja bahkan untuk daftar heterogen karena System.Object bisa menjadi T juga. Melakukan hal ini akan menyelamatkan Anda dari sakit kepala jika Anda memutuskan untuk menggunakan Stack atau struktur data lain di ujung jalan. Jika semua yang perlu Anda lakukan dalam fungsi ini adalah untuk setiap langkah, IEnumerable <T> benar-benar yang harus Anda tanyakan.

Di sisi lain, saat mengembalikan objek dari suatu fungsi, Anda ingin memberi pengguna rangkaian operasi terkaya tanpa harus dilemparkan. Jadi dalam hal itu, jika itu Daftar <T> secara internal, kembalikan salinan sebagai Daftar <T>.

Lee
sumber
43
Anda tidak boleh memperlakukan tipe input / output secara berbeda. Jenis input dan output keduanya harus menjadi tipe paling dasar (lebih disukai antarmuka) yang akan mendukung kebutuhan klien. Enkapsulasi bergantung pada memberi tahu klien sesedikit mungkin tentang implementasi kelas Anda. Jika Anda mengembalikan Daftar yang konkret, Anda tidak dapat mengubah ke jenis lain yang lebih baik tanpa memaksa semua klien Anda mengkompilasi ulang / memperbarui.
Ash
11
Saya tidak setuju dengan 2 aturan ... Saya akan menggunakan tipe yang paling primitif dan spesial ketika kembali dalam hal ini IList (IEnumarable lebih baik) dan Anda harus bekerja dengan List dalam fungsi Anda di dalam. Kemudian ketika Anda perlu "menambah" atau "mengurutkan" maka gunakan Koleksi jika perlu lebih dari itu gunakan Daftar. Jadi aturan keras saya adalah: MULAI selalu dengan IENumarable dan jika Anda membutuhkan lebih maka perluas ...
ethem
2
Untuk kenyamanan Anda, "dua aturan" memiliki nama: prinsip ketahanan (alias hukum Postel) .
easoncxz
Sisi mana pun dari perdebatan seseorang tentang apakah akan mengembalikan tipe paling dasar atau tipe terkaya, sesuatu yang perlu dipertimbangkan adalah bahwa ketika mengembalikan antarmuka yang sangat sederhana, kode konsumsi seringkali - walaupun tidak selalu - menggunakan if...elserantai dengan iskata kunci untuk mencari keluar tipe yang lebih kaya untuk itu dan akhirnya casting untuk itu dan menggunakannya pula. Jadi Anda tidak perlu dijamin menyembunyikan apa pun dengan menggunakan antarmuka dasar, bukan hanya mengaburkannya. Namun membuatnya lebih sulit juga dapat membuat penulis kode konsumsi berpikir dua kali tentang bagaimana mereka menggunakannya.
Panzercrisis
6
Saya sangat tidak setuju tentang Poin # 2, terutama jika ini adalah pada batas layanan / api. Mengembalikan koleksi yang dapat dimodifikasi dapat memberi kesan bahwa koleksi tersebut "langsung" dan metode pemanggilan seperti Add()dan Remove()mungkin memiliki efek di luar koleksi saja. Mengembalikan antarmuka hanya baca seperti IEnumerablesering kali merupakan cara untuk mencari metode pengambilan data. Konsumen Anda dapat memproyeksikannya menjadi tipe yang lebih kaya sesuai kebutuhan.
STW
56

Pedoman Microsoft sebagaimana dicentang oleh FxCop mencegah penggunaan Daftar <T> di API publik - lebih memilih IList <T>.

Kebetulan, saya sekarang hampir selalu mendeklarasikan array satu dimensi sebagai IList <T>, yang berarti saya dapat secara konsisten menggunakan properti IList <T> .Count daripada Array.Length. Sebagai contoh:

public interface IMyApi
{
    IList<int> GetReadOnlyValues();
}

public class MyApiImplementation : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        List<int> myList = new List<int>();
        ... populate list
        return myList.AsReadOnly();
    }
}
public class MyMockApiImplementationForUnitTests : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        IList<int> testValues = new int[] { 1, 2, 3 };
        return testValues;
    }
}
Joe
sumber
3
Saya paling suka penjelasan / contoh ini!
JonH
28

Ada hal penting yang selalu dilupakan orang:

Anda bisa meneruskan array sederhana ke sesuatu yang menerima IList<T>parameter, dan kemudian Anda bisa memanggil IList.Add()dan akan menerima pengecualian runtime:

Unhandled Exception: System.NotSupportedException: Collection was of a fixed size.

Misalnya, pertimbangkan kode berikut:

private void test(IList<int> list)
{
    list.Add(1);
}

Jika Anda memanggilnya sebagai berikut, Anda akan mendapatkan pengecualian runtime:

int[] array = new int[0];
test(array);

Ini terjadi karena menggunakan array sederhana dengan IList<T>melanggar prinsip substitusi Liskov.

Untuk alasan ini, jika Anda menelepon IList<T>.Add()Anda mungkin ingin mempertimbangkan untuk meminta List<T>alih - alih IList<T>.

Matthew Watson
sumber
Ini sepele berlaku untuk setiap antarmuka. Jika Anda ingin menindaklanjuti dengan argumen Anda, daripada Anda bisa berdebat untuk tidak pernah menggunakan antarmuka sama sekali, karena beberapa implementasi itu mungkin melempar. Jika Anda, di sisi lain, mempertimbangkan saran yang diberikan oleh OP untuk List<T>lebih dipilih IList<T>, Anda juga harus mengetahui alasan mengapa IList<T>direkomendasikan. (Misalnya blogs.msdn.microsoft.com/kcwalina/2005/09/26/… )
Micha Wiedenmann
3
@MichaWiedenmann Jawaban saya di sini khusus untuk saat Anda menelepon IList<T>.Add(). Saya tidak mengatakan bahwa Anda tidak boleh menggunakan IList<T>- Saya hanya menunjukkan kemungkinan jebakan. (Saya cenderung menggunakan IEnumerable<T>atau IReadOnlyList<T>atau IReadOnlyCollection<T>lebih suka IList<T>jika saya bisa.)
Matthew Watson
24

Saya setuju dengan saran Lee untuk mengambil parameter, tetapi tidak kembali.

Jika Anda menentukan metode Anda untuk mengembalikan antarmuka itu berarti Anda bebas untuk mengubah implementasi yang tepat di kemudian hari tanpa metode konsumsi yang pernah tahu. Saya pikir saya tidak perlu mengubah dari Daftar <T> tetapi kemudian harus mengubah untuk menggunakan pustaka daftar kustom untuk fungsionalitas tambahan yang disediakan. Karena saya hanya mengembalikan IList <T> tidak ada orang yang menggunakan perpustakaan harus mengubah kode mereka.

Tentu saja itu hanya perlu diterapkan pada metode yang terlihat secara eksternal (yaitu metode publik). Saya pribadi menggunakan antarmuka bahkan dalam kode internal, tetapi karena Anda dapat mengubah semua kode sendiri jika Anda membuat perubahan melanggar itu tidak sepenuhnya diperlukan.

ICR
sumber
22

IEnumerable
Anda harus mencoba dan menggunakan jenis paling tidak spesifik yang sesuai dengan tujuan Anda.
IEnumerablekurang spesifik dari IList.
Anda gunakan IEnumerablesaat Anda ingin mengulang item dalam koleksi.


IListMengimplementasikan IListIEnumerable .
Anda harus menggunakannya IListsaat Anda membutuhkan akses berdasarkan indeks ke koleksi Anda, menambah dan menghapus elemen, dll ...

Daftar
List alat IList.

rajesh
sumber
3
Luar biasa, jawaban yang jelas, yang saya tandai sangat membantu. Namun, saya akan menambahkan bahwa untuk sebagian besar pengembang, sebagian besar waktu, perbedaan kecil dalam ukuran dan kinerja program tidak layak dikhawatirkan: jika ragu, cukup gunakan Daftar.
Graham Laight
9

Itu selalu terbaik untuk menggunakan jenis pangkalan serendah mungkin. Ini memberi pelaksana antarmuka Anda, atau konsumen metode Anda, kesempatan untuk menggunakan apa pun yang mereka suka di belakang layar.

Untuk koleksi, Anda harus berupaya menggunakan IEnumerable jika memungkinkan. Ini memberikan fleksibilitas paling tetapi tidak selalu cocok.

tgmdbm
sumber
1
Itu selalu terbaik untuk menerima jenis pangkalan serendah mungkin. Kembali adalah cerita yang berbeda. Pilih opsi apa yang mungkin berguna. Jadi Anda pikir klien Anda mungkin ingin menggunakan akses yang diindeks? Jaga agar mereka tidak ToList()mengembalikan Anda IEnumerable<T>yang sudah menjadi daftar, dan IList<T>sebaliknya mengembalikan . Sekarang, klien dapat mengambil manfaat dari apa yang dapat Anda berikan tanpa usaha.
Timo
5

Jika Anda bekerja dalam satu metode (atau bahkan dalam satu kelas atau perakitan dalam beberapa kasus) dan tidak ada orang luar yang akan melihat apa yang Anda lakukan, gunakan kepenuhan Daftar. Tetapi jika Anda berinteraksi dengan kode luar, seperti ketika Anda mengembalikan daftar dari suatu metode, maka Anda hanya ingin mendeklarasikan antarmuka tanpa harus mengikat diri Anda pada implementasi tertentu, terutama jika Anda tidak memiliki kontrol atas siapa yang mengkompilasi terhadap Anda kode sesudahnya. Jika Anda mulai dengan tipe konkret dan Anda memutuskan untuk beralih ke yang lain, meskipun itu menggunakan antarmuka yang sama, Anda akan merusak kode orang lain kecuali Anda memulai dengan antarmuka atau tipe basis abstrak.

Mark Cidade
sumber
4

Saya tidak berpikir ada aturan keras dan cepat untuk hal semacam ini, tapi saya biasanya mengikuti pedoman menggunakan cara yang paling ringan sampai benar-benar diperlukan.

Sebagai contoh, katakanlah Anda memiliki Personkelas dan Groupkelas. Sebuah Groupcontoh memiliki banyak orang, sehingga Daftar di sini akan masuk akal. Ketika saya mendeklarasikan objek daftar di Groupsaya akan menggunakan IList<Person>dan instantiate sebagai List.

public class Group {
  private IList<Person> people;

  public Group() {
    this.people = new List<Person>();
  }
}

Dan, jika Anda bahkan tidak membutuhkan semua yang ada di dalamnya, IListAnda selalu dapat menggunakannya IEnumerablejuga. Dengan kompiler dan prosesor modern, saya tidak berpikir ada perbedaan kecepatan sebenarnya, jadi ini hanya masalah gaya.

swilliams
sumber
3
mengapa tidak menjadikannya sebagai Daftar saja? Saya masih tidak mengerti mengapa bonus yang Anda dapatkan dari menjadikannya IList, kemudian di konstruktor Anda membuatnya menjadi Daftar <>
chobo2
Saya setuju, jika Anda secara eksplisit membuat objek <T> Daftar maka Anda kehilangan keunggulan antarmuka?
The_Butcher
4

Anda paling sering lebih baik menggunakan jenis yang paling umum digunakan, dalam hal ini IList atau bahkan lebih baik antarmuka IEnumerable, sehingga Anda dapat beralih implementasi dengan mudah di lain waktu.

Namun, dalam. NET 2.0, ada hal yang menjengkelkan - IList tidak memiliki metode Sort () . Anda dapat menggunakan adaptor yang disediakan sebagai gantinya:

ArrayList.Adapter(list).Sort()
petr k.
sumber
2

Anda harus menggunakan antarmuka hanya jika Anda membutuhkannya, misalnya, jika daftar Anda dilemparkan ke implementasi IList selain Daftar. Ini benar ketika, misalnya, Anda menggunakan NHibernate, yang melemparkan ILists ke objek tas NHibernate saat mengambil data.

Jika Daftar adalah satu-satunya implementasi yang akan Anda gunakan untuk koleksi tertentu, silakan menyatakannya sebagai implementasi Daftar yang konkret.

Jon Limjap
sumber
1

Dalam situasi yang biasanya saya temui, saya jarang menggunakan IList secara langsung.

Biasanya saya hanya menggunakannya sebagai argumen untuk suatu metode

void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

Ini akan memungkinkan saya untuk melakukan pemrosesan generik pada hampir semua array dalam kerangka NET., Kecuali menggunakan IEnumerable dan bukan IList, yang kadang-kadang terjadi.

Ini benar-benar turun ke jenis fungsi yang Anda butuhkan. Saya sarankan menggunakan kelas Daftar dalam banyak kasus. IList adalah yang terbaik untuk saat Anda perlu membuat larik kustom yang dapat memiliki beberapa aturan yang sangat spesifik yang ingin Anda enkapsulasi dalam koleksi sehingga Anda tidak mengulangi diri Anda sendiri, tetapi tetap ingin .NET mengenalinya sebagai daftar.

Dan Herbert
sumber
1

Objek AList memungkinkan Anda untuk membuat daftar, menambahkan sesuatu ke dalamnya, menghapusnya, memperbaruinya, mengindeks ke dalamnya dan lain-lain. Daftar digunakan kapan pun Anda hanya ingin Daftar generik tempat Anda menentukan jenis objek di dalamnya dan hanya itu.

IList di sisi lain adalah Interface. Pada dasarnya, jika Anda ingin membuat tipe Daftar Anda sendiri, ucapkan kelas daftar yang disebut BookList, maka Anda dapat menggunakan Antarmuka untuk memberi Anda metode dan struktur dasar untuk kelas baru Anda. IList adalah untuk saat Anda ingin membuat sendiri sub-kelas Anda yang mengimplementasikan Daftar.

Perbedaan lainnya adalah: IList adalah Antarmuka dan tidak dapat dipakai. Daftar adalah kelas dan bisa dipakai. Itu berarti:

IList<string> MyList = new IList<string>();

List<string> MyList = new List<string>
Javid
sumber