Koleksi <T> versus Daftar <T> apa yang harus Anda gunakan pada antarmuka Anda?

162

Kode terlihat seperti di bawah ini:

namespace Test
{
    public interface IMyClass
    {
        List<IMyClass> GetList();
    }

    public class MyClass : IMyClass
    {
        public List<IMyClass> GetList()
        {
            return new List<IMyClass>();
        }
    }
}

Ketika saya Menjalankan Analisis Kode saya mendapatkan rekomendasi berikut.

Peringatan 3 CA1002: Microsoft.Design: Ubah 'Daftar' di 'IMyClass.GetList ()' untuk menggunakan Collection, ReadOnlyCollection atau KeyedCollection

Bagaimana saya harus memperbaikinya dan apa praktik yang baik di sini?

bovium
sumber

Jawaban:

234

Untuk menjawab bagian "mengapa" dari pertanyaan tentang mengapa tidak List<T>, Alasannya adalah bukti masa depan dan kesederhanaan API.

Pemeriksaan masa depan

List<T>tidak dirancang agar mudah diperluas dengan mensubklasifikasikannya; itu dirancang agar cepat untuk implementasi internal. Anda akan melihat metode di atasnya tidak virtual dan jadi tidak bisa diganti, dan tidak ada kait ke Add/ Insert/ Removeoperasi.

Ini berarti bahwa jika Anda perlu mengubah perilaku koleksi di masa mendatang (misalnya untuk menolak objek nol yang orang coba tambahkan, atau untuk melakukan pekerjaan tambahan ketika ini terjadi seperti memperbarui status kelas Anda) maka Anda perlu mengubah jenisnya koleksi yang Anda kembalikan ke subclass yang Anda bisa, yang akan menjadi perubahan antarmuka yang melanggar (tentu saja mengubah semantik hal-hal seperti tidak mengizinkan nol juga bisa menjadi perubahan antarmuka, tetapi hal-hal seperti memperbarui keadaan kelas internal Anda tidak akan menjadi).

Jadi dengan mengembalikan salah satu kelas yang dapat dengan mudah disubklasifikasikan seperti Collection<T>atau antarmuka seperti IList<T>, ICollection<T>atau IEnumerable<T>Anda dapat mengubah implementasi internal Anda menjadi jenis koleksi yang berbeda untuk memenuhi kebutuhan Anda, tanpa melanggar kode konsumen karena masih dapat dikembalikan sebagai tipe yang mereka harapkan.

Kesederhanaan API

List<T>mengandung banyak operasi yang bermanfaat seperti BinarySearch, Sortdan sebagainya. Namun jika ini adalah koleksi yang Anda paparkan maka kemungkinan Anda mengendalikan semantik dari daftar, dan bukan konsumen. Jadi, sementara kelas Anda secara internal mungkin memerlukan operasi ini, sangat tidak mungkin bahwa konsumen kelas Anda ingin (atau bahkan harus) memanggil mereka.

Dengan demikian, dengan menawarkan kelas koleksi atau antarmuka yang lebih sederhana, Anda mengurangi jumlah anggota yang dilihat pengguna API Anda, dan membuatnya lebih mudah untuk mereka gunakan.

Greg Beech
sumber
1
Respons ini sudah mati. Bacaan bagus lainnya tentang masalah ini dapat ditemukan di sini: blogs.msdn.com/fxcop/archive/2006/04/27/…
senfo
7
Saya melihat poin pertama Anda, tetapi saya tidak tahu apakah saya menyetujui bagian kesederhanaan API Anda.
Boris Callens
stackoverflow.com/a/398988/2632991 Ini adalah posting yang sangat bagus juga, tentang apa perbedaan antara Koleksi dan Daftar.
El Mac
2
blogs.msdn.com/fxcop/archive/2006/04/27/… -> Sudah mati. Apakah kami memiliki informasi yang lebih baru untuk ini?
Machado
50

Saya pribadi akan mendeklarasikannya untuk mengembalikan antarmuka daripada koleksi nyata. Jika Anda benar-benar ingin akses daftar, gunakan IList<T>. Kalau tidak, pertimbangkan ICollection<T>dan IEnumerable<T>.

Jon Skeet
sumber
1
Apakah IList kemudian memperluas antarmuka ICollection?
bovium
8
@ Jon: Saya tahu ini sudah tua, tetapi dapatkah Anda mengomentari apa yang dikatakan Krzysztof di blogs.msdn.com/b/kcwalina/archive/2005/09/26/474010.aspx ? Khusus komentarnya, We recommend using Collection<T>, ReadOnlyCollection<T>, or KeyedCollection<TKey,TItem> for outputs and properties and interfaces IEnumerable<T>, ICollection<T>, IList<T> for inputs. CA1002 tampaknya sejalan dengan komentar Krzysztof. Saya tidak bisa membayangkan mengapa koleksi beton akan direkomendasikan sebagai ganti antarmuka, dan mengapa perbedaan antara input / output.
Nelson Rothermel
3
@Nelson: Jarang sekali Anda ingin meminta penelepon untuk memberikan daftar yang tidak dapat diubah, tetapi masuk akal untuk mengembalikannya sehingga mereka tahu itu pasti tidak dapat diubah. Tidak yakin tentang koleksi lainnya. Akan menyenangkan untuk memiliki lebih banyak detail.
Jon Skeet
2
Ini bukan untuk kasus tertentu. Jelas secara umum ReadOnlyCollection<T>tidak masuk akal untuk input. Demikian pula, IList<T>seperti input mengatakan, "Saya perlu Sort () atau anggota lain yang dimiliki IList" yang tidak masuk akal untuk sebuah output. Tapi yang saya maksudkan adalah, mengapa akan ICollection<T>direkomendasikan sebagai input dan Collection<T>sebagai output. Mengapa tidak menggunakan ICollection<T>sebagai output juga seperti yang Anda sarankan?
Nelson Rothermel
9
Saya pikir ini ada hubungannya dengan ketidakjelasan. Collection<T>dan ReadOnlyCollection<T>keduanya berasal dari ICollection<T>(yaitu tidak ada IReadOnlyCollection<T>). Jika Anda mengembalikan antarmuka, tidak jelas yang mana dan apakah dapat dimodifikasi. Bagaimanapun, terima kasih atas masukan Anda. Ini adalah pemeriksaan kewarasan yang baik untuk saya.
Nelson Rothermel
3

Saya tidak berpikir ada yang menjawab bagian "mengapa" ... jadi begini. Alasan "mengapa" Anda "harus" menggunakan kata Collection<T>ganti a List<T>adalah karena jika Anda mengekspos huruf a List<T>, maka siapa pun yang mendapatkan akses ke objek Anda dapat memodifikasi item dalam daftar. Padahal Collection<T>seharusnya menunjukkan bahwa Anda membuat sendiri metode "Tambah", "Hapus", dll.

Anda mungkin tidak perlu khawatir tentang hal itu, karena Anda mungkin mengkodekan antarmuka hanya untuk diri sendiri (atau mungkin beberapa kolega). Inilah contoh lain yang mungkin masuk akal.

Jika Anda memiliki susunan publik, mis:

public int[] MyIntegers { get; }

Anda akan berpikir itu karena hanya ada accessor "get" yang tidak ada yang bisa mengacaukan nilai-nilai, tetapi itu tidak benar. Siapa pun dapat mengubah nilai di dalam sana seperti ini:

someObject.MyIngegers[3] = 12345;

Secara pribadi, saya hanya akan menggunakan List<T>dalam banyak kasus. Tetapi jika Anda merancang perpustakaan kelas yang akan Anda berikan kepada pengembang acak, dan Anda harus bergantung pada keadaan objek ... maka Anda akan ingin membuat Koleksi Anda sendiri dan menguncinya dari sana: )

Timothy Khouri
sumber
"Jika Anda mengembalikan Daftar <T> ke kode klien, Anda tidak akan pernah dapat menerima notifikasi ketika kode klien memodifikasi koleksi." - FX COP ... Lihat juga " msdn.microsoft.com/en-us/library/0fss9skc.aspx " ... wow, sepertinya saya sama sekali tidak pangkalan :)
Timothy Khouri
Wow - bertahun-tahun kemudian saya melihat bahwa tautan yang saya tempelkan di komentar di atas memiliki kutipan di bagian akhir, sehingga tidak berhasil ... msdn.microsoft.com/en-us/library/0fss9skc.aspx
Timothy Khouri
2

Ini sebagian besar tentang abstrak implementasi Anda sendiri daripada mengekspos objek Daftar untuk dimanipulasi secara langsung.

Bukan praktik yang baik untuk membiarkan objek lain (atau orang) mengubah keadaan objek Anda secara langsung. Pikirkan getter / setter properti.

Koleksi -> Untuk koleksi normal,
ReadOnlyCollection -> Untuk koleksi yang tidak boleh dimodifikasi
KeyedCollection -> Saat Anda menginginkan kamus.

Cara memperbaikinya tergantung pada apa yang ingin dilakukan kelas Anda dan tujuan metode GetList (). Bisakah Anda menguraikan?

chakrit
sumber
1
Tetapi Koleksi <T> dan KeyedCollection <,> juga tidak dapat dibaca.
nawfal
@nawfal Dan di mana saya mengatakan itu?
chakrit
1
Anda menyatakan tidak membiarkan objek lain (atau orang) memodifikasi keadaan objek Anda secara langsung dan mendaftar 3 tipe koleksi. Kecuali ReadOnlyCollectiondua yang lain tidak mematuhinya.
nawfal
Itu merujuk pada "praktik yang baik". Harap konteks yang tepat. Daftar itu di bawah ini hanya menyatakan persyaratan dasar dari jenis seperti itu karena OP ingin memahami peringatan itu. Saya kemudian bertanya kepadanya tentang tujuan GetList()agar dapat membantunya dengan lebih benar.
chakrit
1
Baiklah saya mengerti bagian itu. Tapi tetap saja alasannya tidak masuk akal menurut saya. Anda mengatakan Collection<T>membantu dalam abstrak implementasi internal dan mencegah manipulasi langsung daftar internal. Bagaimana? Collection<T>hanyalah pembungkus, beroperasi pada contoh yang sama berlalu. Ini koleksi dinamis yang dimaksudkan untuk warisan, tidak ada yang lain (jawaban Greg lebih relevan di sini).
nawfal
1

Dalam kasus seperti ini saya biasanya mencoba untuk mengekspos jumlah implementasi yang paling sedikit yang diperlukan. Jika konsumen tidak perlu tahu bahwa Anda benar-benar menggunakan daftar maka Anda tidak perlu mengembalikan daftar. Dengan kembali saat Microsoft menyarankan Koleksi, Anda menyembunyikan fakta bahwa Anda menggunakan daftar dari konsumen kelas Anda dan mengisolasi mereka terhadap perubahan internal.

Harald Scheirich
sumber
1

Sesuatu untuk ditambahkan meskipun sudah lama sejak ini diminta.

Ketika jenis daftar Anda berasal dari List<T>bukan Collection<T>, Anda tidak bisa menerapkan metode virtual terlindungi yang Collection<T>mengimplementasikan. Ini artinya bahwa tipe turunan Anda tidak dapat merespons jika modifikasi dilakukan pada daftar. Ini karena List<T>menganggap Anda sadar ketika Anda menambah atau menghapus item. Mampu menanggapi pemberitahuan adalah overhead dan karenanya List<T>tidak menawarkannya.

Dalam kasus ketika kode eksternal memiliki akses ke koleksi Anda, Anda mungkin tidak dapat mengendalikan kapan suatu item ditambahkan atau dihapus. Karena itu, Collection<T>berikan cara untuk mengetahui kapan daftar Anda diubah.

NullReference
sumber
0

Saya tidak melihat masalah dengan mengembalikan sesuatu seperti

this.InternalData.Filter(crteria).ToList();

Jika saya mengembalikan salinan data internal yang terputus , atau hasil permintaan data yang terlepas - saya dapat kembali dengan aman List<TItem>tanpa memaparkan detail implementasi, dan memungkinkan untuk menggunakan data yang dikembalikan dengan cara yang mudah.

Tapi ini tergantung pada jenis konsumen apa yang saya harapkan - jika ini adalah sesuatu seperti data grid saya lebih suka mengembalikan IEnumerable<TItem> yang akan menjadi daftar item yang disalin dalam banyak kasus :)

Konstantin Isaev
sumber
-1

Ya, kelas Collection sebenarnya hanya kelas pembungkus di sekitar koleksi lain untuk menyembunyikan detail implementasi dan fitur lainnya. Saya rasa ini ada hubungannya dengan properti menyembunyikan pola pengkodean dalam bahasa berorientasi objek.

Saya pikir Anda tidak perlu khawatir tentang itu, tetapi jika Anda benar-benar ingin menyenangkan alat analisis kode, lakukan saja hal berikut:

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);
Tamas Czinege
sumber
1
Maaf, itu salah ketik. Saya menat Collection <MyClass>. Saya benar-benar berharap untuk mendapatkan C # 4 dan kovarians generik btw!
Tamas Czinege
Membungkus dalam Collection<T>melindungi baik itu sendiri maupun koleksi yang mendasarinya sejauh yang saya mengerti.
nawfal
Sepotong kode itu untuk "tolong alat analisis kode". Saya tidak berpikir @TamasCzinege mengatakan di mana saja bahwa menggunakan Collection<T>akan segera melindungi koleksi Anda yang mendasarinya.
chakrit