Apakah diizinkan menggunakan implementasi antarmuka eksplisit untuk menyembunyikan anggota di C #?

14

Saya mengerti bagaimana cara bekerja dengan antarmuka dan implementasi antarmuka eksplisit dalam C #, tapi saya bertanya-tanya apakah itu dianggap bentuk yang buruk untuk menyembunyikan anggota tertentu yang tidak akan sering digunakan. Sebagai contoh:

public interface IMyInterface
{
    int SomeValue { get; set; }
    int AnotherValue { get; set; }
    bool SomeFlag { get; set; }

    event EventHandler SomeValueChanged;
    event EventHandler AnotherValueChanged;
    event EventHandler SomeFlagChanged;
}

Saya tahu sebelumnya bahwa acara, meskipun bermanfaat, akan sangat jarang digunakan. Jadi, saya pikir akan masuk akal untuk menyembunyikan mereka dari kelas implementasi melalui implementasi eksplisit untuk menghindari kekacauan IntelliSense.

Kyle Baran
sumber
3
Jika Anda mereferensikan instance Anda melalui antarmuka, toh itu tidak akan menyembunyikannya, itu hanya akan menyembunyikan jika referensi Anda adalah tipe konkret.
Andy
1
Saya bekerja dengan seorang pria yang melakukan ini secara teratur. Itu benar-benar membuatku gila.
Joel Etherton
... dan mulai menggunakan agregasi.
mikalai

Jawaban:

39

Ya, itu bentuk umumnya buruk.

Jadi, saya pikir akan masuk akal untuk menyembunyikan mereka dari kelas implementasi melalui implementasi eksplisit untuk menghindari kekacauan IntelliSense.

Jika IntelliSense Anda terlalu berantakan, kelas Anda terlalu besar. Jika kelas Anda terlalu besar, kemungkinan melakukan terlalu banyak hal dan / atau dirancang dengan buruk.

Perbaiki masalah Anda, bukan gejala Anda.

Telastyn
sumber
Apakah pantas untuk menggunakannya untuk menghapus peringatan kompiler tentang anggota yang tidak digunakan?
Gusdor
11
"Perbaiki masalah Anda, bukan gejala Anda." +1
FreeAsInBeer
11

Gunakan fitur untuk apa yang dimaksudkan untuk itu. Mengurangi kekacauan namespace bukan salah satunya.

Alasan yang sah bukan tentang "persembunyian" atau organisasi, itu untuk menyelesaikan ambiguitas dan untuk berspesialisasi. Jika Anda menerapkan dua antarmuka yang mendefinisikan "Foo", semantik untuk Foo mungkin berbeda. Benar-benar bukan cara yang lebih baik untuk menyelesaikan masalah ini kecuali untuk antarmuka eksplisit. Alternatifnya adalah melarang penerapan antarmuka yang tumpang tindih, atau menyatakan bahwa antarmuka tidak dapat mengaitkan semantik (yang sebenarnya hanyalah hal konseptual). Keduanya merupakan alternatif yang buruk. Terkadang kepraktisan mengalahkan keanggunan.

Beberapa penulis / ahli menganggapnya sebagai kludge dari suatu fitur (metode ini tidak benar-benar bagian dari model objek tipe ini). Tapi seperti semua fitur, di suatu tempat, seseorang membutuhkannya.

Sekali lagi, saya tidak akan menggunakannya untuk persembunyian atau organisasi. Ada cara untuk menyembunyikan anggota dari intellisense.

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
codenheim
sumber
"Ada penulis / pakar terkemuka yang menganggapnya sebagai sebuah fitur." Siapa yang? Apa alternatif yang diusulkan? Setidaknya ada satu masalah (yaitu spesialisasi metode antarmuka dalam subinterface / kelas implementasi) yang akan memerlukan beberapa fitur lain untuk ditambahkan ke C # untuk menyelesaikannya dengan tidak adanya implementasi eksplisit (lihat ADO.NET dan koleksi umum) .
jhominal
@jhominal - CLR via C # oleh Jeffrey Richter secara khusus menggunakan kata kludge. C ++ bergaul tanpanya, programmer harus secara eksplisit merujuk pada implementasi metode dasar untuk disambiguasi, atau menggunakan pemain. Saya sudah menyebutkan bahwa alternatifnya tidak terlalu menarik. Tetapi ini merupakan aspek warisan tunggal dan antarmuka, bukan OOP secara umum.
codenheim
Anda juga bisa menyebutkan bahwa Java juga tidak memiliki implementasi eksplisit, meskipun memiliki desain "pewarisan tunggal implementasi + antarmuka" yang sama. Namun, di Jawa, tipe kembalinya metode overriden dapat dikhususkan, meniadakan bagian dari kebutuhan untuk implementasi eksplisit. (Dalam C #, keputusan desain yang berbeda dibuat, mungkin sebagian karena penyertaan properti).
jhominal
@jhominal - Tentu saja, ketika saya punya beberapa menit kemudian saya akan memperbarui. Terima kasih.
codenheim
Menyembunyikan mungkin sepenuhnya sesuai dalam situasi di mana kelas mungkin menerapkan metode antarmuka dengan cara yang sesuai dengan kontrak antarmuka tetapi tidak berguna . Sebagai contoh, sementara saya tidak menyukai gagasan kelas di mana IDisposable.Disposemelakukan sesuatu tetapi diberi nama yang berbeda seperti Close, saya akan menganggapnya masuk akal untuk kelas yang kontraknya secara tegas menyangkal menyatakan bahwa kode dapat membuat dan meninggalkan contoh tanpa memanggil Disposeuntuk menggunakan implementasi antarmuka eksplisit untuk mendefinisikan IDisposable.Disposemetode yang tidak melakukan apa-apa.
supercat
5

Saya mungkin menjadi tanda kode berantakan, tetapi kadang-kadang dunia nyata berantakan. Salah satu contoh dari .NET framework adalah ReadOnlyColection yang menyembunyikan setter yang bermutasi [], Add and Set.

IE ReadOnlyCollection mengimplementasikan IList <T>. Lihat juga pertanyaan saya ke SO beberapa tahun yang lalu ketika saya merenungkan ini.

Esben Skov Pedersen
sumber
Yah itu antarmuka saya sendiri yang akan saya gunakan juga, jadi tidak ada gunanya melakukan kesalahan sejak awal.
Kyle Baran
2
Saya tidak akan mengatakan itu menyembunyikan setter yang bermutasi tetapi itu tidak memberikannya sama sekali. Contoh yang lebih baik adalah ConcurrentDictionary, yang mengimplementasikan berbagai anggota IDictionary<T>secara eksplisit sehingga konsumen ConcurrentDictionaryA) tidak akan memanggil mereka secara langsung dan B) dapat menggunakan metode yang memerlukan implementasi IDictionary<T>jika benar-benar diperlukan. Misalnya, pengguna ConcurrentDictionary<T> harus menelepon TryAdddaripada Addmenghindari perlu pengecualian yang tidak perlu.
Brian
Tentu saja, menerapkan Addsecara eksplisit juga mengurangi kekacauan. TryAdddapat melakukan apa saja yang Adddapat dilakukan ( Adddiimplementasikan sebagai panggilan untuk TryAdd, sehingga menyediakan keduanya akan berlebihan). Namun, TryAddtentu memiliki nama / tanda tangan yang berbeda, sehingga tidak mungkin diterapkan IDictionarytanpa redundansi ini. Implementasi eksplisit menyelesaikan masalah ini dengan bersih.
Brian
Nah dari sudut pandang konsumen metode tersebut disembunyikan.
Esben Skov Pedersen
1
Anda menulis tentang IReadonlyCollection <T> tetapi tautan ke ReadOnlyCollection <T>. Yang pertama dalam sebuah antarmuka, yang kedua adalah kelas. IReadonlyCollection <T> tidak mengimplementasikan IList <T> meskipun ReadonlyCollection <T> melakukannya.
Jørgen Fogh
2

Ini adalah bentuk yang baik untuk melakukannya ketika anggota tidak ada artinya dalam konteks. Misalnya, jika Anda membuat koleksi hanya baca yang mengimplementasikan IList<T>dengan mendelegasikan ke objek internal _wrappedmaka Anda mungkin memiliki sesuatu seperti:

public T this[int index]
{
  get
  {
     return _wrapped[index];
  }
}
T IList<T>.this[int index]
{
  get
  {
    return this[index];
  }
  set
  {
    throw new NotSupportedException("Collection is read-only.");
  }
}
public int Count
{
  get { return _wrapped.Count; }
}
bool ICollection<T>.IsReadOnly
{
  get
  {
    return true;
  }
}

Di sini kita punya empat kasus berbeda.

public T this[int index]didefinisikan oleh kelas kita daripada antarmuka, dan karenanya tentu saja bukan implementasi eksplisit, meskipun perhatikan bahwa itu memang mirip dengan baca-tulis yang T this[int index]didefinisikan dalam antarmuka tetapi hanya baca-saja.

T IList<T>.this[int index]eksplisit karena salah satu bagiannya (pengambil) sangat cocok dengan properti di atas, dan bagian lainnya akan selalu melemparkan pengecualian. Sementara penting bagi seseorang yang mengakses instance kelas ini melalui antarmuka, tidak ada gunanya bagi seseorang yang menggunakannya melalui variabel tipe kelas.

Demikian pula karena bool ICollection<T>.IsReadOnlyselalu akan mengembalikan true itu sama sekali tidak ada gunanya untuk kode yang ditulis terhadap tipe kelas, tetapi bisa sangat penting untuk menggunakannya melalui tipe antarmuka, dan oleh karena itu kami menerapkannya secara eksplisit.

Sebaliknya, public int Counttidak diterapkan secara eksplisit karena berpotensi dapat digunakan untuk seseorang yang menggunakan instance melalui tipe sendiri.

Tetapi dengan kasus "sangat jarang digunakan" Anda, saya akan cenderung sangat kuat untuk tidak menggunakan implementasi eksplisit.

Dalam kasus di mana saya merekomendasikan menggunakan implementasi eksplisit memanggil metode melalui variabel dari tipe kelas akan menjadi kesalahan (mencoba untuk menggunakan setter yang diindeks) atau tidak berguna (memeriksa nilai yang akan selalu sama) sehingga dalam bersembunyi mereka Anda melindungi pengguna dari kode kereta atau sub-optimal. Itu sangat berbeda dengan kode yang Anda pikir cenderung jarang digunakan. Untuk itu saya mungkin mempertimbangkan untuk menggunakan EditorBrowsableatribut untuk menyembunyikan anggota dari intellisense, meskipun bahkan saya akan bosan; otak orang sudah memiliki perangkat lunak mereka sendiri untuk menyaring apa yang tidak menarik bagi mereka.

Jon Hanna
sumber
Jika Anda mengimplementasikan antarmuka, terapkan antarmuka. Kalau tidak, ini jelas merupakan pelanggaran terhadap Prinsip Pergantian Liskov.
Telastyn
@ Telastyn antarmuka memang diimplementasikan.
Jon Hanna
Tetapi kontrak itu tidak puas - khususnya, "Ini adalah sesuatu yang mewakili koleksi akses acak yang bisa berubah".
Telastyn
1
@Telastyn memenuhi kontrak yang didokumentasikan, terutama mengingat "Koleksi yang hanya baca tidak memungkinkan penambahan, penghapusan, atau modifikasi elemen setelah koleksi dibuat." Lihat msdn.microsoft.com/en-us/library/0cfatk9t%28v=vs.110%29.aspx
Jon Hanna
@ Telastyn: Jika kontrak menyertakan kemampuan berubah-ubah, lalu mengapa antarmuka berisi IsReadOnlyproperti?
nikie