Bagaimana cara membuat Kelas yang tidak dapat diubah?

113

Saya sedang membuat kelas yang tidak bisa diubah.
Saya telah menandai semua properti sebagai hanya-baca.

Saya memiliki daftar item di kelas.
Meskipun jika properti adalah read-only, daftar dapat diubah.

Mengekspos IE yang tak terhitung banyaknya dari daftar membuatnya tidak bisa diubah.
Saya ingin tahu apa aturan dasar yang harus diikuti untuk membuat kelas tidak berubah?

Biswanath
sumber
2
Saya sangat menganjurkan agar Anda membaca seri blog Eric Lippert tentang kekekalan , khususnya entri tentang "jenis kekekalan" . Komentar Anda bahwa "Mengekspos IE yang tak terhitung banyaknya dari daftar membuatnya tidak dapat diubah" tampaknya agak aneh bagi saya. Apa yang Anda maksud dengan itu?
Jon Skeet
Yang saya maksud adalah, daripada mengizinkan untuk mengakses daftar (Jika objek memiliki daftar beberapa objek lain), memungkinkan pengguna untuk mengakses anggota dengan IEnumerable. Daftar pembicaraan di sini seperti dalam contoh spesifik, tetapi dapat berupa struktur data apa pun.
Biswanath
1
Tautan Jon Skeet ke seri blog Eric Lippert rusak saat MSDN mengarsipkan blog ini. Silakan lihat "jenis kekekalan" . Sisa seri tampaknya berada di bawah Desember 2007 + Januari 2008 di panel sebelah kiri.
John Doe
Silakan lihat seri blog Eric Lippert tentang bagaimana orang umumnya bingung istilah atomicity, volatility, dan immutability: Bagian Satu , Bagian Kedua , dan Bagian Ketiga . Ini dari blog pribadinya dan, saya yakin, lebih ramah pemula daripada posting MSDN miliknya.
John Doe

Jawaban:

121

Saya pikir Anda berada di jalur yang benar -

  • semua informasi yang dimasukkan ke dalam kelas harus disediakan di konstruktor
  • semua properti harus menjadi getter saja
  • jika koleksi (atau Array) diteruskan ke konstruktor, itu harus disalin agar pemanggil tidak mengubahnya nanti
  • jika Anda akan mengembalikan koleksi Anda, kembalikan salinan atau versi hanya-baca (misalnya, menggunakan ArrayList.ReadOnly atau serupa - Anda dapat menggabungkan ini dengan poin sebelumnya dan menyimpan salinan hanya-baca untuk dikembalikan saat pemanggil mengaksesnya), mengembalikan enumerator, atau menggunakan beberapa metode / properti lain yang memungkinkan akses hanya-baca ke dalam koleksi
  • perlu diingat bahwa Anda masih mungkin memiliki tampilan kelas yang bisa berubah jika ada anggota Anda yang bisa berubah - jika ini masalahnya, Anda harus menyalin keadaan apa pun yang ingin Anda pertahankan dan menghindari mengembalikan seluruh objek yang bisa berubah, kecuali Anda menyalinnya sebelum mengembalikannya ke penelepon - opsi lainnya adalah mengembalikan hanya "bagian" yang tidak dapat diubah dari objek yang dapat berubah - terima kasih kepada @Brian Rasmussen yang telah mendorong saya untuk memperluas poin ini
Blair Conrad
sumber
Haruskah saya menulis properti wrapper look up, yang memungkinkan Anda hanya melakukan pencarian? Dan terima kasih atas jawabannya.
Biswanath
5
Jenis referensi yang bisa berubah yang diteruskan sebagai argumen ke konstruktor harus disalin. Jika tidak, penelepon akan tetap memiliki referensi ke negara bagian.
Brian Rasmussen
@ Brian Rasmussen, Anda benar, tetapi meskipun disalin, mungkin bagi pemanggil mana pun untuk mengakses objek yang dapat berubah, tergantung pada acces.sors yang disediakan. Dalam kasus melewati objek yang bisa berubah, kelas yang terbaik adalah selalu mengembalikan salinan yang berbeda, atau bagian objek yang tidak berubah
Blair Conrad
@Blair - Jika saya memiliki kamus di kelas yang hanya ingin saya gunakan untuk pencarian. Apakah properti hanya-baca yang pencariannya baik-baik saja?
Biswanath
@Biswanath - ya, dengan peringatan bahwa jika nilai dalam kamus dapat berubah, Anda perlu melindunginya sebelum kembali, jika Anda tidak ingin mereka bermutasi
Blair Conrad
18

Agar tidak dapat diubah, semua properti dan bidang Anda harus hanya-baca. Dan item dalam daftar mana pun harus tidak dapat diubah.

Anda dapat membuat properti daftar hanya-baca sebagai berikut:

public class MyClass
{
    public MyClass(..., IList<MyType> items)
    {
        ...
        _myReadOnlyList = new List<MyType>(items).AsReadOnly();
    }

    public IList<MyType> MyReadOnlyList
    {
        get { return _myReadOnlyList; }
    }
    private IList<MyType> _myReadOnlyList

}
Joe
sumber
1
Saya pikir ImmutableList akan lebih baik.
Kevin Wong
gunakan ImmutableList, pertimbangkan juga untuk menyegel kelas
kofifus
Saya tidak yakin bahwa ImmutableList adalah cocok untuk kasus penggunaan ini: basic rules to make a class (that contains a list) immutable. Semantik ImmutableList memungkinkan penggunaan memori yang lebih efisien saat menambahkan item ke salinan daftar, tetapi menurut saya itu adalah kasus penggunaan yang lebih spesifik.
Joe
11

Selain itu, perlu diingat bahwa:

public readonly object[] MyObjects;

tidak dapat diubah meskipun ditandai dengan kata kunci hanya baca. Anda masih dapat mengubah referensi / nilai array individu dengan pengakses indeks.

lubos hasko
sumber
5

Gunakan ReadOnlyCollectionkelas. Itu terletak diSystem.Collections.ObjectModel namespace.

Pada apa pun yang mengembalikan daftar Anda (atau dalam konstruktor), setel daftar sebagai koleksi hanya-baca.

using System.Collections.ObjectModel;

...

public MyClass(..., List<ListItemType> theList, ...)
{
    ...
    this.myListItemCollection= theList.AsReadOnly();
    ...
}

public ReadOnlyCollection<ListItemType> ListItems
{
     get { return this.myListItemCollection; }
}
mbillard.dll
sumber
1

Pilihan lainnya adalah menggunakan pola pengunjung daripada mengekspos koleksi internal sama sekali.

Andrew
sumber
1

Menggunakan ReadOnlyCollection akan membatasi klien untuk mengubahnya.

Aku marah
sumber