it-swarm-id.com

Mengapa tidak mungkin menimpa properti pengambil-saja dan menambahkan setter?

Mengapa kode C # berikut tidak diizinkan:

public abstract class BaseClass
{
    public abstract int Bar { get;}
}

public class ConcreteClass : BaseClass
{
    public override int Bar
    {
        get { return 0; }
        set {}
    }
}

CS0546 'ConcreteClass.Bar.set': tidak dapat menimpa karena 'BaseClass.Bar' tidak memiliki set accessor yang dapat ditimpa

130
ripper234

Karena penulis Baseclass secara eksplisit menyatakan bahwa Bar harus menjadi properti hanya-baca. Tidak masuk akal jika derivasi melanggar kontrak ini dan membuatnya baca-tulis.

Saya dengan Microsoft yang satu ini.
Katakanlah saya seorang programmer baru yang telah diberi tahu kode terhadap derivasi Baseclass. saya menulis sesuatu yang mengasumsikan bahwa Bar tidak dapat ditulisi (karena Baseclass secara eksplisit menyatakan bahwa itu adalah properti get only) ..__ Sekarang dengan derivasi Anda, kode saya mungkin rusak. misalnya.

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

  public void setSource(BaseClass b)
  {
    _source = b;
    _currentBar = b.Bar;
  }

  public Bar getBar()
  { return _currentBar;  }
}

Karena Bar tidak dapat ditetapkan sesuai antarmuka BaseClass, BarProvider mengasumsikan bahwa caching adalah hal yang aman untuk dilakukan - Karena Bar tidak dapat dimodifikasi. Tetapi jika set dimungkinkan dalam derivasi, kelas ini bisa menyajikan nilai basi jika seseorang memodifikasi properti Bar objek _source secara eksternal. Intinya adalah 'Jadilah Terbuka, hindari melakukan hal-hal licik dan mengejutkan orang'

Perbarui : Ilya Ryzhenkov bertanya 'Mengapa antarmuka tidak bermain dengan aturan yang sama?' Hmm .. ini menjadi lebih kacau saat saya memikirkannya.
Antarmuka adalah kontrak yang mengatakan 'harapkan implementasi memiliki properti baca bernama Bar.' Secara pribadi Saya lebih kecil kemungkinannya untuk membuat asumsi hanya-baca jika saya melihat Antarmuka. Ketika saya melihat properti get-only pada antarmuka, saya membacanya sebagai 'Implementasi apa pun akan mengekspos bar atribut ini' ... pada kelas dasar itu klik sebagai 'Bar adalah properti read-only'. Tentu saja secara teknis Anda tidak melanggar kontrak .. Anda melakukan lebih banyak. Jadi Anda benar dalam beberapa hal .. Saya akan menutup dengan mengatakan 'buat sesulit mungkin untuk kesalahpahaman muncul'.

1
Gishu

Saya pikir alasan utamanya adalah karena sintaksinya terlalu eksplisit untuk dapat bekerja dengan cara lain. Kode ini:

public override int MyProperty { get { ... } set { ... } }

cukup eksplisit bahwa baik get dan set adalah yang dikesampingkan. Tidak ada set di kelas dasar, jadi kompiler mengeluh. Sama seperti Anda tidak bisa mengganti metode yang tidak didefinisikan di kelas dasar, Anda juga tidak bisa mengganti setter.

Anda mungkin mengatakan bahwa kompiler harus menebak niat Anda dan hanya menerapkan override ke metode yang dapat diganti (yaitu pengambil dalam kasus ini), tetapi ini bertentangan dengan salah satu prinsip desain C # - bahwa kompiler tidak boleh menebak niat Anda , karena mungkin salah menebak tanpa Anda sadari.

Saya pikir sintaks berikut mungkin bekerja dengan baik, tetapi seperti Eric Lippert terus katakan, mengimplementasikan bahkan fitur kecil seperti ini masih merupakan upaya besar ...

public int MyProperty
{
    override get { ... }
    set { ... }
}

atau, untuk properti yang diimplementasikan secara otomatis,

public int MyProperty { override get; set; }
31
Roman Starkov

Saya menemukan masalah yang sama hari ini dan saya pikir memiliki alasan yang sangat valid untuk menginginkan ini. 

Pertama saya ingin berdebat bahwa memiliki properti get-only tidak harus diterjemahkan menjadi read-only. Saya menafsirkannya sebagai "Dari antarmuka ini/abtract Anda bisa mendapatkan nilai ini", itu tidak berarti bahwa beberapa implementasi dari antarmuka/kelas abstrak tidak memerlukan pengguna/program untuk mengatur nilai ini secara eksplisit. Kelas abstrak melayani tujuan mengimplementasikan bagian dari fungsionalitas yang diperlukan. Saya sama sekali tidak melihat alasan mengapa kelas yang diwariskan tidak dapat menambahkan setter tanpa melanggar kontrak apa pun.

Berikut ini adalah contoh sederhana dari apa yang saya butuhkan hari ini. Saya akhirnya harus menambahkan setter di antarmuka saya hanya untuk menyiasati ini. Alasan untuk menambahkan setter dan tidak menambahkan, katakanlah, metode SetProp adalah bahwa salah satu implementasi khusus antarmuka menggunakan DataContract/DataMember untuk serialisasi Prop, yang akan dibuat rumit tanpa perlu jika saya harus menambahkan properti lain hanya untuk tujuan serialisasi.

interface ITest
{
    // Other stuff
    string Prop { get; }
}

// Implements other stuff
abstract class ATest : ITest
{
    abstract public string Prop { get; }
}

// This implementation of ITest needs the user to set the value of Prop
class BTest : ATest
{
    string foo = "BTest";
    public override string Prop
    {
        get { return foo; }
        set { foo = value; } // Not allowed. 'BTest.Prop.set': cannot override because 'ATest.Prop' does not have an overridable set accessor
    }
}

// This implementation of ITest generates the value for Prop itself
class CTest : ATest
{
    string foo = "CTest";
    public override string Prop
    {
        get { return foo; }
        // set; // Not needed
    }
}

Saya tahu ini hanya posting "2 sen" saya, tetapi saya merasa dengan poster asli dan mencoba merasionalisasi bahwa ini adalah hal yang baik tampaknya aneh bagi saya, terutama mengingat keterbatasan yang sama tidak berlaku ketika mewarisi langsung dari sebuah warisan antarmuka.

Juga menyebutkan tentang menggunakan yang baru alih-alih menimpa tidak berlaku di sini, itu hanya tidak berfungsi dan bahkan jika itu tidak akan memberi Anda hasil yang diinginkan, yaitu pengambil virtual seperti yang dijelaskan oleh antarmuka.

19
JJJ

Itu mungkin

tl; dr - Anda dapat mengganti metode get-only dengan setter jika Anda mau. Pada dasarnya itu hanya:

  1. Buat properti new yang memiliki get dan set menggunakan nama yang sama.

  2. Jika Anda tidak melakukan hal lain, maka metode get lama masih akan dipanggil ketika kelas turunan dipanggil melalui tipe dasarnya. Untuk memperbaikinya, tambahkan abstract lapisan antara yang menggunakan override pada metode get lama untuk memaksanya mengembalikan hasil metode get baru.

Ini memungkinkan kita untuk mengganti properti dengan getset bahkan jika mereka tidak memiliki satu dalam definisi dasar mereka.

Sebagai bonus, Anda juga dapat mengubah jenis pengembalian jika diinginkan.

  • Jika definisi dasar hanya get-, maka Anda dapat menggunakan tipe pengembalian yang lebih diturunkan.

  • Jika definisi dasar hanya set-, maka Anda dapat menggunakan tipe pengembalian yang kurang diturunkan.

  • Jika definisi dasar sudah get/set, maka:

    • anda bisa menggunakan tipe pengembalian yang lebih diturunkan _/if Anda membuatnya set- saja;

    • anda dapat menggunakan tipe pengembalian yang kurang diturunkan if Anda membuatnya get- saja.

Dalam semua kasus, Anda dapat menyimpan tipe pengembalian yang sama jika Anda mau. Contoh di bawah ini menggunakan tipe pengembalian yang sama untuk kesederhanaan.

Situasi: Properti yang hanya ada get-

Anda memiliki beberapa struktur kelas yang tidak dapat Anda modifikasi. Mungkin hanya satu kelas, atau itu pohon warisan yang sudah ada sebelumnya. Apa pun masalahnya, Anda ingin menambahkan metode set ke properti, tetapi tidak bisa.

public abstract class A                     // Pre-existing class; can't modify
{
    public abstract int X { get; }          // You want a setter, but can't add it.
}
public class B : A                          // Pre-existing class; can't modify
{
    public override int X { get { return 0; } }
}

Masalah: Tidak dapat override the get- hanya dengan getset

Anda ingin override dengan properti get/set, tetapi tidak dapat dikompilasi.

public class C : B
{
    private int _x;
    public override int X
    {
        get { return _x; }
        set { _x = value; }   //  Won't compile
    }
}

Solusi: Gunakan lapisan antara abstract

Meskipun Anda tidak dapat secara langsung override dengan properti get/set, Anda _/can:

  1. Buat properti newget/set dengan nama yang sama.

  2. override metode get lama dengan accessor ke metode get baru untuk memastikan konsistensi.

Jadi, pertama Anda menulis lapisan antara abstract:

public abstract class C : B
{
    //  Seal off the old getter.  From now on, its only job
    //  is to alias the new getter in the base classes.
    public sealed override int X { get { return this.XGetter; }  }
    protected abstract int XGetter { get; }
}

Kemudian, Anda menulis kelas yang tidak bisa dikompilasi sebelumnya. Ini akan mengkompilasi saat ini karena Anda tidak benar-benar override hanya properti get-; alih-alih, Anda menggantinya menggunakan kata kunci new.

public class D : C
{
    private int _x;
    public new virtual int X { get { return this._x; } set { this._x = value; } }

    //  Ensure base classes (A,B,C) use the new get method.
    protected sealed override int XGetter { get { return this.X; } }
}

Hasil: Semuanya berfungsi!

Jelas, ini berfungsi sebagaimana dimaksudkan untuk D.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

test.X = 7;
Print(test.X);      // Prints "7", as intended.

Semuanya masih berfungsi sebagaimana dimaksudkan saat melihat D sebagai salah satu kelas dasar, mis. A atau B. Tapi, alasan mengapa itu bekerja mungkin agak kurang jelas.

var test = new D() as B;
//test.X = 7;       // This won't compile, because test looks like a B,
                    // and B still doesn't provide a visible setter.

Namun, definisi kelas dasar dari get masih pada akhirnya ditimpa oleh definisi kelas turunan dari get, jadi itu masih sepenuhnya konsisten.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

var baseTest = test as A;
Print(test.X);      // Prints "7", as intended.

Diskusi

Metode ini memungkinkan Anda untuk menambahkan metode set ke get- properti saja. Anda juga dapat menggunakannya untuk melakukan hal-hal seperti:

  1. Ubah properti apa pun menjadi properti get- saja, set-, atau get- dan -set, terlepas dari apa itu di kelas dasar.

  2. Ubah tipe pengembalian metode dalam kelas turunan.

Kelemahan utama adalah bahwa ada lebih banyak pengkodean untuk dilakukan dan abstract class tambahan di pohon warisan. Ini bisa sedikit menjengkelkan dengan konstruktor yang mengambil parameter karena harus disalin/ditempelkan di lapisan perantara.

12
Nat

Saya setuju bahwa tidak bisa mengganti pengambil dalam tipe turunan adalah anti-pola. Read-Only menentukan kurangnya implementasi, bukan kontrak fungsional murni (tersirat oleh jawaban suara teratas).

Saya menduga Microsoft memiliki batasan ini baik karena kesalahpahaman yang sama dipromosikan, atau mungkin karena menyederhanakan tata bahasa; meskipun, sekarang ruang lingkup itu dapat diterapkan untuk mendapatkan atau mengatur secara individual, mungkin kita bisa berharap menimpanya juga.

Kesalahpahaman yang ditunjukkan oleh jawaban suara terbanyak, bahwa properti baca-saja entah bagaimana harus lebih "murni" daripada properti baca/tulis itu konyol. Cukup dengan melihat banyak properti read only yang umum dalam framework; nilainya tidak konstan/murni fungsional; misalnya, DateTime. Sekarang hanya baca, tetapi apa pun kecuali nilai fungsional murni. Upaya untuk 'men-cache' nilai properti hanya baca dengan asumsi akan mengembalikan nilai yang sama lain kali berisiko.

Bagaimanapun, saya telah menggunakan salah satu strategi berikut untuk mengatasi batasan ini; keduanya kurang sempurna, tetapi akan memungkinkan Anda pincang melampaui kekurangan bahasa ini:

   class BaseType
   {
      public virtual T LastRequest { get {...} }
   }

   class DerivedTypeStrategy1
   {
      /// get or set the value returned by the LastRequest property.
      public bool T LastRequestValue { get; set; }

      public override T LastRequest { get { return LastRequestValue; } }
   }

   class DerivedTypeStrategy2
   {
      /// set the value returned by the LastRequest property.
      public bool SetLastRequest( T value ) { this._x = value; }

      public override T LastRequest { get { return _x; } }

      private bool _x;
   }
8
T.Tobler

Masalahnya adalah bahwa untuk alasan apa pun Microsoft memutuskan bahwa harus ada tiga jenis properti: baca-saja, hanya-tulis, dan baca-tulis, hanya satu yang mungkin ada dengan tanda tangan yang diberikan dalam konteks tertentu; properti hanya dapat diganti oleh properti yang dideklarasikan secara identik. Untuk melakukan apa yang Anda inginkan, Anda perlu membuat dua properti dengan nama dan tanda tangan yang sama - satu di antaranya hanya baca, dan satu di antaranya adalah baca-tulis.

Secara pribadi, saya berharap bahwa seluruh konsep "properti" dapat dihapuskan, kecuali bahwa sintaksis properti-ish dapat digunakan sebagai gula sintaksis untuk memanggil metode "get" dan "set". Ini tidak hanya memfasilitasi opsi 'add set', tetapi juga akan memungkinkan 'get' untuk mengembalikan tipe yang berbeda dari 'set'. Sementara kemampuan seperti itu tidak akan sering digunakan, kadang-kadang bisa berguna untuk memiliki metode 'dapatkan' mengembalikan objek pembungkus sementara 'set' dapat menerima pembungkus atau data aktual.

1
supercat

Saya dapat memahami semua poin Anda, tetapi secara efektif, properti otomatis C # 3.0 menjadi tidak berguna dalam hal ini.

Anda tidak dapat melakukan hal seperti itu:

public class ConcreteClass : BaseClass
{
    public override int Bar
    {
        get;
        private set;
    }
}

IMO, C # seharusnya tidak membatasi skenario seperti itu. Merupakan tanggung jawab pengembang untuk menggunakannya.

1
Thomas Danecker

Anda mungkin dapat mengatasi masalah dengan membuat properti baru:

public new int Bar 
{            
    get { return 0; }
    set {}        
}

int IBase.Bar { 
  get { return Bar; }
}
1
Hallgrim

Solusi hanya untuk sebagian kecil kasus penggunaan, namun demikian: di C # 6.0 setter "readonly" secara otomatis ditambahkan untuk properti pengambil-override saja.

public abstract class BaseClass
{
    public abstract int Bar { get; }
}

public class ConcreteClass : BaseClass
{
    public override int Bar { get; }

    public ConcreteClass(int bar)
    {
        Bar = bar;
    }
}
0
lxa

Anda harus mengubah judul pertanyaan Anda menjadi detail bahwa pertanyaan Anda semata-mata berkaitan dengan mengesampingkan properti abstrak, atau bahwa pertanyaan Anda adalah sehubungan dengan umumnya mengesampingkan properti hanya-mendapatkan kelas.


Jika yang pertama (menimpa properti abstrak)

Kode itu tidak berguna. Kelas dasar saja seharusnya tidak memberi tahu Anda bahwa Anda dipaksa untuk menimpa properti Get-Only (Mungkin Antarmuka). Kelas dasar menyediakan fungsionalitas umum yang mungkin memerlukan input spesifik dari kelas pelaksana. Oleh karena itu, fungsi umum dapat membuat panggilan ke properti atau metode abstrak. Dalam kasus yang diberikan, metode fungsionalitas umum harus meminta Anda untuk mengganti metode abstrak seperti:

public int GetBar(){}

Tetapi jika Anda tidak memiliki kendali atas itu, dan fungsi dari kelas dasar membaca dari milik publiknya sendiri (aneh), maka lakukan saja ini:

public abstract class BaseClass
{
    public abstract int Bar { get; }
}

public class ConcreteClass : BaseClass
{
    private int _bar;
    public override int Bar
    {
        get { return _bar; }
    }
    public void SetBar(int value)
    {
        _bar = value;
    }
}

Saya ingin menunjukkan komentar (aneh): Saya akan mengatakan praktik terbaik adalah untuk kelas tidak menggunakan properti publiknya sendiri, tetapi untuk menggunakan bidang privat/dilindungi ketika ada. Jadi ini adalah pola yang lebih baik:

public abstract class BaseClass {
    protected int _bar;
    public int Bar { get { return _bar; } }
    protected void DoBaseStuff()
    {
        SetBar();
        //Do something with _bar;
    }
    protected abstract void SetBar();
}

public class ConcreteClass : BaseClass {
    protected override void SetBar() { _bar = 5; }
}

Jika yang terakhir (menimpa properti get-only kelas)

Setiap properti non-abstrak memiliki setter. Kalau tidak, itu tidak berguna dan Anda tidak perlu peduli untuk menggunakannya. Microsoft tidak harus mengizinkan Anda untuk melakukan apa yang Anda inginkan. Alasannya: setter ada dalam beberapa bentuk atau lainnya, dan Anda dapat mencapai apa yang Anda inginkan Veerryy dengan mudah.

Kelas dasar, atau kelas mana pun di mana Anda dapat membaca properti dengan {get;}, memilikiSOMEsemacam setter yang terbuka untuk properti itu. Metadata akan terlihat seperti ini:

public abstract class BaseClass
{
    public int Bar { get; }
}

Tetapi implementasinya akan memiliki dua ujung spektrum kompleksitas:

Kompleks Paling Sedikit:

public abstract class BaseClass
{
    private int _bar;
    public int Bar { 
        get{
            return _bar;
        }}
    public void SetBar(int value) { _bar = value; }
}

Paling Kompleks:

public abstract class BaseClass
{
    private int _foo;
    private int _baz;
    private int _wtf;
    private int _kthx;
    private int _lawl;

    public int Bar
    {
        get { return _foo * _baz + _kthx; }
    }
    public bool TryDoSomethingBaz(MyEnum whatever, int input)
    {
        switch (whatever)
        {
            case MyEnum.lol:
                _baz = _lawl + input;
                return true;
            case MyEnum.wtf:
                _baz = _wtf * input;
                break;
        }
        return false;
    }
    public void TryBlowThingsUp(DateTime when)
    {
        //Some Crazy Madeup Code
        _kthx = DaysSinceEaster(when);
    }
    public int DaysSinceEaster(DateTime when)
    {
        return 2; //<-- calculations
    }
}
public enum MyEnum
{
    lol,
    wtf,
}

Maksud saya, bagaimanapun, Anda memiliki setter terbuka. Dalam kasus Anda, Anda mungkin ingin menimpa int Bar karena Anda tidak ingin kelas dasar menanganinya, tidak memiliki akses untuk meninjau cara penanganannya, atau ditugaskan untuk membajak beberapa kode dengan sangat cepat'n'dirty terhadap Anda akan.

Dalam Latter dan Former (Kesimpulan)

Cerpen Panjang: Tidak perlu bagi Microsoft untuk mengubah apa pun. Anda dapat memilih bagaimana kelas implementasi Anda diatur dan, sans konstruktor, gunakan semua atau tidak ada kelas dasar.

0
Suamere

Berikut ini adalah solusi untuk mencapai hal ini menggunakan Refleksi:

var UpdatedGiftItem = // object value to update;

foreach (var proInfo in UpdatedGiftItem.GetType().GetProperties())
{
    var updatedValue = proInfo.GetValue(UpdatedGiftItem, null);
    var targetpropInfo = this.GiftItem.GetType().GetProperty(proInfo.Name);
    targetpropInfo.SetValue(this.GiftItem, updatedValue,null);
}

Dengan cara ini kita dapat menetapkan nilai objek pada properti yang hanya bisa dibaca. Namun, mungkin tidak berfungsi di semua skenario!

0
user2514880