Bagaimana cara menangani setter pada bidang yang tidak berubah?

25

Saya memiliki kelas dengan dua readonly intbidang. Mereka terpapar sebagai properti:

public class Thing
{
    private readonly int _foo, _bar;

    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public int Foo { get { return _foo; } set { } }

    public int Bar { get { return _bar; } set { } }
}

Namun, itu berarti bahwa kode berikut ini benar-benar legal:

Thing iThoughtThisWasMutable = new Thing(1, 42);

iThoughtThisWasMutable.Foo = 99;  // <-- Poor, mistaken developer.
                                  //     He surely has bugs now. :-(

Bug yang berasal dari asumsi itu akan berhasil pasti akan membahayakan. Tentu, pengembang yang salah seharusnya membaca dokumen. Tapi itu tidak mengubah fakta bahwa tidak ada kesalahan kompilasi atau run-time yang memperingatkannya tentang masalah tersebut.

Bagaimana seharusnya Thingkelas diubah sehingga para pengembang cenderung membuat kesalahan di atas?

Melempar pengecualian? Gunakan metode pengambil alih-alih properti?

kdbanman
sumber
1
Terima kasih, @gnat. Posting yang (baik pertanyaan dan jawaban) tampaknya akan berbicara tentang interface, seperti di Capital saya Interfaces. Saya tidak yakin itu yang saya lakukan.
kdbanman
6
@gnat, itu saran yang mengerikan. Antarmuka menawarkan cara hebat untuk melayani VO / DTO yang tidak bisa diubah secara publik yang masih mudah diuji.
David Arno
4
@gnat, saya lakukan dan pertanyaannya tidak masuk akal karena OP tampaknya berpikir bahwa antarmuka akan menghancurkan ketidakberdayaan, yang tidak masuk akal.
David Arno
1
@gnat, pertanyaan itu adalah tentang mendeklarasikan antarmuka untuk DTO. Jawaban pilihan teratas adalah bahwa antarmuka tidak akan berbahaya, tetapi mungkin tidak perlu.
Paul Draper

Jawaban:

107

Mengapa membuat kode itu legal?
Keluarkan set { }jika tidak melakukan apa-apa.
Ini adalah cara Anda mendefinisikan properti publik hanya baca:

public int Foo { get { return _foo; } }
paparazzo
sumber
3
Saya tidak berpikir itu sah untuk beberapa alasan, tetapi tampaknya berfungsi dengan baik! Sempurna, terima kasih.
kdbanman
1
@kdbanman Mungkin ada hubungannya dengan sesuatu yang Anda lihat IDE lakukan pada satu titik - mungkin penyelesaian otomatis.
Panzercrisis
2
@kdbanman; apa yang tidak legal (hingga C # 5) adalah public int Foo { get; }(properti otomatis dengan pengambil) public int Foo { get; private set; }.
Laurent LA RIZZA
@LaurentLARIZZA, Anda telah menghabiskan ingatan saya. Dari sanalah kebingungan saya berasal.
kdbanman
45

Dengan C # 5 dan sebelumnya, kami dihadapkan dengan dua opsi untuk bidang yang tidak berubah yang diekspos melalui pengambil:

  1. Buat variabel dukungan hanya-baca dan kembalikan itu melalui pengambil manual. Opsi ini aman (seseorang harus secara eksplisit menghapus readonlyuntuk menghancurkan ketidakberdayaan. Ini membuat banyak kode boiler-plate.
  2. Gunakan properti otomatis, dengan setter pribadi. Ini menciptakan kode yang lebih sederhana, tetapi lebih mudah untuk secara tidak sengaja memutus kekekalan.

Dengan C # 6 (tersedia di VS2015, yang dirilis kemarin), kami sekarang mendapatkan yang terbaik dari kedua dunia: properti otomatis hanya-baca. Ini memungkinkan kami untuk menyederhanakan kelas OP menjadi:

public class Thing
{
    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; }
    public int Bar { get; }
}

Garis Foo = foodan Bar = barhanya valid dalam konstruktor (yang mencapai persyaratan read-only yang kuat) dan bidang dukungan tersirat, daripada harus didefinisikan secara eksplisit (yang mencapai kode lebih sederhana).

David Arno
sumber
2
Dan konstruktor utama dapat menyederhanakan ini lebih jauh, dengan menghilangkan overhead sintaksis dari konstruktor.
Sebastian Redl
3
@SebastianRedl sayangnya, konstruktor utama telah dihapus dari rilis C # 6 final .
Dan Lyons
1
@DanLyons Sialan, saya berharap untuk menggunakan ini di seluruh basis kode kami.
Sebastian Redl
12

Anda bisa saja menyingkirkan setter. Mereka tidak melakukan apa-apa, mereka akan membingungkan pengguna dan mereka akan menyebabkan bug. Namun, Anda bisa menjadikannya pribadi dan dengan demikian menyingkirkan variabel pendukung, menyederhanakan kode Anda:

public class Thing
{
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; private set; }

    public int Bar { get; private set; }
}
David Arno
sumber
1
" public int Foo { get; private set; }" Kelas itu tidak lagi abadi karena bisa berubah sendiri. Terimakasih Meskipun!
kdbanman
4
Kelas yang dijelaskan tidak dapat diubah, karena tidak berubah dengan sendirinya.
David Arno
1
Kelas saya yang sebenarnya sedikit lebih kompleks dari yang saya berikan kepada MWE . Saya mengincar kekekalan nyata , lengkap dengan keamanan benang dan jaminan kelas internal.
kdbanman
4
@kdbanman, dan maksud Anda adalah? Jika kelas Anda hanya menulis ke setter pribadi selama konstruksi, maka kelas tersebut telah menerapkan "immutabilitas nyata". Para readonlykata kunci hanya poka-yoke, yaitu itu penjaga terhadap kelas melanggar immutablity di masa depan; itu tidak diperlukan untuk mencapai keabadian.
David Arno
3
Istilah yang menarik, saya harus mencarinya di Google - poka-yoke adalah istilah Jepang yang berarti "pembuktian kesalahan". Kamu benar! Itulah tepatnya yang saya lakukan.
kdbanman
6

Dua solusi.

Sederhana:

Jangan sertakan setter seperti yang dicatat oleh David untuk objek yang hanya bisa dibaca yang tidak berubah.

Kalau tidak:

Izinkan setter untuk mengembalikan objek yang tidak dapat diubah baru, verbose dibandingkan dengan yang sebelumnya tetapi memberikan status dari waktu ke waktu untuk setiap objek yang diinisialisasi. Desain ini adalah alat yang sangat berguna untuk keselamatan dan kekekalan Thread yang mencakup semua bahasa OOP yang penting.

Kodesemu

public class Thing
{

{readonly vars}

    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}

pabrik statis publik

public class Thing
{

{readonly vars}

    private Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public static with(int foo, int bar) {
        return new Thing(foo, bar)
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}
Matt Smithies
sumber
2
setXYZ adalah nama yang mengerikan untuk metode pabrik, pertimbangkan denganXYZ atau sejenisnya.
Ben Voigt
Terima kasih, perbarui jawaban saya untuk mencerminkan komentar Anda yang bermanfaat. :-)
Matt Smithies
Pertanyaannya adalah menanyakan secara khusus tentang setter properti , bukan metode setter.
user253751
Benar, tetapi OP khawatir tentang keadaan yang bisa berubah dari pengembang masa depan yang mungkin . Variabel yang menggunakan readonly pribadi atau kata kunci akhir pribadi harus mendokumentasikan diri sendiri, itu bukan kesalahan devs asli jika itu tidak dipahami. Memahami berbagai metode untuk mengubah keadaan adalah kuncinya. Saya telah menambahkan pendekatan lain yang sangat membantu. Ada banyak hal dalam dunia kode yang menyebabkan paranoia yang berlebihan, vars pribadi hanya baca tidak boleh menjadi salah satu dari mereka.
Matt Smithies