Adakah kata kunci dalaman C # bau kod?

Dalam catatan ini, saya akan menunjukkan mengapa saya berpendapat bahawa kata kunci dalaman, apabila diletakkan pada ahli kelas, adalah bau kod dan mencadangkan alternatif yang lebih baik.

Apakah kata kunci dalaman?

Dalam C # kata kunci dalaman boleh digunakan pada kelas atau anggotanya. Ini adalah salah satu pengubah akses C #. Jenis atau ahli dalaman hanya boleh diakses dalam fail dalam pemasangan yang sama . (C # dokumentasi kata kunci dalaman).

Mengapa kita memerlukan kata kunci dalaman?

"Penggunaan umum akses dalaman adalah dalam pengembangan berbasis komponen karena memungkinkan sekelompok komponen untuk bekerja sama secara tertutup tanpa terkena kode aplikasi yang lain . Sebagai contoh, kerangka kerja untuk membina antara muka pengguna grafik dapat menyediakan kelas Kawalan dan Bentuk yang bekerjasama dengan menggunakan anggota dengan akses dalaman. Oleh kerana anggota ini bersifat dalaman, mereka tidak terkena kod yang menggunakan kerangka kerja. " (C # dokumentasi kata kunci dalaman)

Ini adalah kes penggunaan yang saya lihat kerana menggunakan kata kunci dalaman pada ahli kelas:

  • Panggil fungsi peribadi kelas dalam perhimpunan yang sama.
  • Untuk menguji fungsi peribadi, anda boleh menandainya sebagai dalaman dan mendedahkan dll ke DLL ujian melalui InternalsVisibleTo.

Kedua-dua kes itu dapat dilihat sebagai bau kod, dengan mengatakan bahawa fungsi peribadi ini harus diketahui umum.

Mari lihat beberapa contoh

Berikut adalah contoh mudah. fungsi dalam satu kelas mahu mengakses fungsi peribadi kelas lain.

class A{ public void func1(){ func2(); } private void func2(){} } class B{ public void func(A a){ a.func2(); //Compilation error 'A.func2()' is inaccessible due to its protection level } }

Penyelesaiannya mudah - tandakan A :: func2 sebagai umum.

Mari lihat contoh yang lebih rumit:

public class A{ public void func1(){} private void func2(B b){} } internal class B{ public void func3(A a){ a.func2(this); //Compilation error 'A.func2(B)' is inaccessible due to its protection level } }

Apa masalahnya? tandakan func2 sebagai umum seperti yang kami lakukan sebelumnya.

public class A{ public void func1(){ ... } public void func2(B b){ ...} // Compilation error: Inconsistent accessibility: parameter type 'B' is less accessible than method 'A.func2(B)' } internal class B{ public void func3(A a){ a.func2(this); } }

Tetapi kita tidak boleh ?. B adalah kelas dalaman sehingga tidak boleh menjadi sebahagian daripada tandatangan fungsi umum kelas awam.

Itulah penyelesaian yang saya dapati, disusun berdasarkan kemudahan:

  1. Tandakan fungsi dengan kata kunci dalaman
public class A{ public void func1(){ } internal void func2(B b){} } internal class B{ public void func3(A a){ a.func2(this); } }

2. Buat antara muka dalaman

internal interface IA2{ void func2(B b); } public class A:IA2{ public void func1(){ var b = new B(); b.func3(this); } void IA2.func2(B b){} //implement IA2 explicitly because func2 can't be public } internal class B{ public void func3(A a){ ((IA2)a).func2(this); //use interface instead of class to access func2 } }

3. Ekstrak A.func2 ke kelas dalaman yang lain dan gunakan sebagai ganti A.func2.

internal class C{ public void func2(B b){ //extract A:func2 to here } } public class A{ public void func1(){} private void func2(B b){ new C().func2(b); } } internal class B{ public void func3(){ //a is no longer needed new C().func2(this); //use internal class instead of private function } }

4. Putuskan fungsi dari kelas dalaman dan buat umum. Ini banyak bergantung pada fungsi yang dilakukan dengan inputnya. mencabut kelas dalaman boleh menjadi sangat mudah, sangat sukar dan bahkan mustahil (tanpa merosakkan reka bentuk).

Tetapi kami tidak mempunyai kelas awam yang kami gunakan antaramuka ...

Mari kita lihat beberapa contoh dunia nyata:

public interface IA{ void func1(); } internal class A : IA { public void func1(){} private void func2(B b){} } internal class B{ public void func3(IA a){ a.func2(this); //Compilation error IA' does not contain a definition for 'func2' and no extension method 'func2' accepting a first argument of type 'IA' could be found } }

Mari kita lihat bagaimana penyelesaian sebelumnya disesuaikan dengan contoh ini:

  1. Tandakan fungsi dengan Dalaman. ini bermaksud anda perlu menghantar ke kelas untuk memanggil fungsi jadi ini akan berfungsi hanya jika kelas A adalah satu-satunya yang melaksanakan antara muka , yang bermaksud IA tidak diejek dalam ujian dan tidak ada kelas produksi lain yang menerapkan IA .
public interface IA{ void func1(); } internal class A : IA { public void func1(){} internal void func2(B b){} } internal class B{ public void func3(IA a){ ((A)a).func2(this); //cast to A in order to accses func2 } }

2. Buat antara muka dalaman yang memperluas antara muka awam.

internal interface IExtendedA : IA{ void func2(B b); } public interface IA{ void func1(); } internal class A : IExtendedA { public void func1(){} public void func2(B b){} } internal class B{ public void func3(IExtendedA a){ a.func2(this); } }

3. Ekstrak A.func2 ke kelas dalaman yang lain dan gunakan sebagai ganti A.func2.

4. Putuskan fungsi dari kelas dalaman dan tambahkan ke antara muka awam.

Kita dapat melihat bahawa kata kunci dalaman adalah penyelesaian yang paling mudah , tetapi ada penyelesaian lain menggunakan blok bangunan tradisional OOP: kelas dan antara muka . Kita dapat melihat bahawa penyelesaian ke-2 - menambahkan antara muka dalaman tidak lebih sukar daripada menandakan fungsi dengan kata kunci dalaman.

Mengapa tidak menggunakan kata kunci dalaman?

Seperti yang saya tunjukkan dalam contoh sebelumnya, menggunakan kata kunci dalaman adalah penyelesaian paling mudah . Tetapi anda akan mengalami kesukaran pada masa akan datang jika anda perlu:

  • Pindahkan kelas awam A ke DLL yang lain (kerana kata kunci dalaman tidak lagi digunakan untuk dll yang sama)
  • Buat kelas pengeluaran lain yang menerapkan IA
  • Mengejek IA dalam ujian

Anda mungkin berfikir "Tetapi ini hanya satu baris kod, saya atau orang lain dapat mengubahnya dengan mudah jika diperlukan". Sekarang anda mempunyai satu baris kod yang kelihatan seperti itu:

((MyClass)a).internalFunction

tetapi jika orang lain perlu memanggil fungsi ini juga baris ini akan disalin-salin di dalam DLL.

Kesimpulan saya

Saya rasa menandakan ahli kelas dengan kata kunci dalaman adalah bau kod . Dalam contoh yang saya tunjukkan di atas, ini adalah penyelesaian paling mudah , TETAPI boleh menyebabkan masalah di masa depan. Membuat antara muka dalaman hampir semudah dan lebih jelas.

Bandingkan dengan C ++

The C++ “friend” keyword is similar to the C# internal keyword. It allows a class or a function to access private members of a class. The difference is it allows access to specific class or function and notall the classes in the same DLL. In my opinion, this is a better solution than the C# internal keyword.

Further Reading

Practical uses for the "internal" keyword in C#

Why does C# not provide the C++ style 'friend' keyword?