Kapan menulis metode ekstensi untuk kelas Anda sendiri?

8

Baru-baru ini saya melihat basis kode yang memiliki kelas data yang Addressditentukan di suatu tempat dan kemudian di tempat yang berbeda:

fun Address.toAnschrift() = let { address ->
    Anschrift().apply {
        // mapping code here...
    }
}

Saya merasa bingung untuk tidak memiliki metode ini pada alamat secara langsung. Apakah ada pola atau praktik terbaik yang ditetapkan saat menggunakan metode ekstensi? Atau apakah buku tentang itu masih harus ditulis?

Perhatikan bahwa terlepas dari sintaksis Kotlin dalam contoh saya, saya tertarik pada praktik terbaik umum karena juga akan berlaku untuk C # atau bahasa lain dengan kemampuan itu.

Robert Jack Will
sumber
4
Dengan "Anschrift" menjadi "alamat" versi Jerman, ini tampaknya merupakan konversi khusus negara dari tipe alamat netral-negara. Ingin menyimpan kode khusus negara di tempat lain masuk akal. Ini bukan jawaban untuk pertanyaan Anda yang sebenarnya, tetapi ini menjelaskan mengapa mereka melakukannya dalam contoh ini .
R. Schmitz
1
»Metode ekstensi memungkinkan Anda untuk" menambahkan "metode ke tipe yang ada tanpa membuat tipe turunan baru, mengkompilasi ulang, atau memodifikasi tipe asli« docs.microsoft.com/en-us/dotnet/csharp/programming-guide/… . »Untuk pustaka kelas yang Anda laksanakan, Anda tidak boleh menggunakan metode ekstensi ... Jika Anda ingin menambahkan fungsionalitas signifikan ke pustaka tempat Anda memiliki kode sumber, Anda harus mengikuti pedoman .NET Framework standar untuk versi perakitan« tl; dr jika Anda memiliki kode, tidak perlu metode ekstensi.
Thomas Junk

Jawaban:

15

Metode penyuluhan tidak menyediakan sesuatu yang tidak bisa dilakukan dengan cara lain. Mereka adalah gula sintaksis untuk membuat pengembangan lebih baik, tanpa memberikan manfaat teknis yang sebenarnya.


Metode penyuluhan relatif baru. Standar dan konvensi biasanya diputuskan oleh pendapat umum (ditambah beberapa pengalaman jangka panjang tentang apa yang akhirnya menjadi ide yang buruk), dan saya tidak berpikir kita pada titik di mana kita dapat menarik garis definitif yang disepakati semua orang.

Namun, saya dapat melihat beberapa argumen, berdasarkan pada hal-hal yang saya dengar dari rekan kerja.

1. Mereka biasanya mengganti metode statis.

Metode ekstensi paling umum didasarkan pada hal-hal yang seharusnya metode statis, bukan metode kelas. Mereka terlihat seperti metode kelas, tetapi sebenarnya tidak.

Metode penyuluhan seharusnya tidak dianggap sebagai alternatif untuk metode kelas; melainkan sebagai cara untuk memberikan tampilan dan nuansa "metodis" yang statis dan sintaksis .

2. Ketika metode yang Anda maksud adalah khusus untuk proyek konsumsi tetapi tidak perpustakaan sumber.

Hanya karena Anda mengembangkan perpustakaan dan konsumen tidak berarti Anda bersedia untuk menempatkan logika di tempat yang cocok.

Sebagai contoh:

  • Anda memiliki PersonDtokelas yang berasal dari DataLayerproyek Anda .
  • WebUIProyek Anda ingin dapat mengonversi a PersonDtoke PersonViewModel.

Anda tidak dapat (dan tidak ingin) menambahkan metode konversi ini ke DataLayerproyek Anda . Karena metode ini termasuk dalam WebUIproyek, ia harus hidup di dalam proyek itu.

Metode ekstensi memungkinkan Anda memiliki metode ini dapat diakses secara global di dalam proyek Anda (dan kemungkinan konsumen proyek Anda), tanpa memerlukan DataLayerperpustakaan untuk mengimplementasikannya.

3. Jika menurut Anda kelas data hanya boleh berisi data.

Misalnya, Personkelas Anda berisi properti untuk seseorang. Tetapi Anda ingin dapat memiliki beberapa metode yang memformat beberapa data:

public class Person
{
    //The properties...

    public SecurityQuestionAnswers GetSecurityAnswers()
    {
        return new SecurityQuestionAnswers()
        {
            MothersMaidenName = this.Mother.MaidenName,
            FirstPetsName = this.Pets.OrderbyDescending(x => x.Date).FirstOrDefault()?.Name
        };
    }
}

Saya telah melihat banyak rekan kerja yang membenci gagasan mencampur data dan logika di kelas. Saya tidak begitu setuju dengan hal-hal sederhana seperti memformat data, tetapi saya mengakui bahwa dalam contoh di atas, rasanya kotor karena Personharus bergantung pada SecurityQuestionAnswers.

Menempatkan metode ini dalam metode ekstensi mencegah pengotoran kelas data yang dinyatakan murni.

Perhatikan bahwa argumen ini adalah salah satu gaya. Ini mirip dengan kelas parsial. Ada sedikit atau tidak ada manfaat teknis untuk melakukannya, tetapi memungkinkan Anda untuk memisahkan kode menjadi beberapa file jika Anda menganggapnya lebih bersih (misalnya jika banyak orang menggunakan kelas tetapi tidak peduli tentang metode kustom tambahan Anda).

4. Metode pembantu

Dalam sebagian besar proyek, saya cenderung berakhir dengan kelas pembantu. Ini adalah kelas statis yang biasanya menyediakan beberapa kumpulan metode untuk pemformatan yang mudah.

Misalnya, satu perusahaan tempat saya bekerja memiliki format datetime tertentu yang ingin mereka gunakan. Daripada harus menempelkan string format ke semua tempat, atau menjadikan string format menjadi variabel global, saya memilih metode ekstensi DateTime:

public static string ToCompanyFormat(this DateTime dt)
{
    return dt.ToString("...");
} 

Apakah itu lebih baik daripada metode pembantu statis normal? Aku pikir begitu. Itu membersihkan sintaks. Dari pada

DateTimeHelper.ToCompanyFormat(myObj.CreatedOn);

Saya bisa melakukan:

myObj.CreatedOn.ToCompanyFormat();

Ini mirip dengan versi lancar dari sintaks yang sama. Saya lebih menyukainya, meskipun tidak ada manfaat teknis dari memiliki satu di atas yang lain.


Semua argumen ini subyektif. Tak satu pun dari mereka yang membahas kasus yang kalau tidak bisa ditutup.

  • Setiap metode ekstensi dapat dengan mudah ditulis ulang menjadi metode statis normal.
  • Kode dapat dipisahkan dalam file menggunakan kelas parsial, bahkan jika metode ekstensi tidak ada.

Tetapi sekali lagi, kami dapat mengambil pernyataan Anda bahwa mereka tidak pernah perlu sampai ekstrem:

  • Mengapa kita memerlukan metode kelas, jika kita selalu dapat membuat metode statis dengan nama yang jelas menunjukkan bahwa itu untuk kelas tertentu?

Jawaban untuk pertanyaan ini, dan pertanyaan Anda, tetap sama:

  • Sintaks yang lebih bagus
  • Peningkatan keterbacaan dan kurang pedantri kode (kode akan langsung ke titik dalam lebih sedikit karakter)
  • Intellisense memberikan hasil yang bermakna secara kontekstual (metode ekstensi hanya ditampilkan pada jenis yang benar. Kelas statis dapat digunakan di mana saja).

Satu tambahan menyebutkan:

  • Metode ekstensi memungkinkan metode chaining gaya pengkodean; yang telah menjadi lebih populer akhir-akhir ini, tidak seperti sintaks pembungkus metode vanilla. Preferensi sintaksis yang serupa dapat dilihat dalam sintaksis metode LINQ.
  • Sedangkan metode chaining dapat dilakukan dengan menggunakan metode kelas juga; perlu diingat bahwa ini tidak cukup berlaku untuk metode statis. Metode ekstensi paling umum didasarkan pada hal-hal yang seharusnya metode statis, bukan metode kelas.
Flater
sumber
5
Poin 2. adalah yang saya temukan paling meyakinkan, dan alasan saya melakukan ini sendiri. Kadang-kadang Anda memiliki kelas yang banyak digunakan di seluruh basis kode Anda, dan metode yang seolah-olah "harus" ada di kelas kecuali bahwa itu melibatkan beberapa proyek atau perpustakaan pihak ketiga yang ingin Anda enkapsulasi. Metode ekstensi untuk kelas Anda sendiri adalah cara yang logis untuk menangani ini.
Astrid
@Astrid: Saya pikir itulah alasan paling teknis untuk melakukannya; Iya.
Flater
Jawaban yang sangat bagus Satu kasus penggunaan khusus C # adalah bahwa antarmuka tidak memiliki perilaku. Metode penyuluhan memungkinkan kita memberi mereka metode.
RubberDuck
Apa yang Anda maksud dengan "manfaat teknis?" Hal-hal seperti kinerja? Keterbacaan kode sama pentingnya, jika tidak lebih dari itu.
Robert Harvey
@ RobertTarvey Secara teknis, maksud saya apa pun itu bukan hanya gula sintaksis. Misalnya, menyebarkan kelas parsial atas banyak file dalam rakitan yang sama tidak memiliki keunggulan teknis. Menyebarkan kelas dan metodenya di berbagai majelis berbeda. Pembacaan kode tentu saja masih penting, tetapi tidak dari sudut pandang teknis.
Flater
5

Saya setuju dengan Flater bahwa belum ada cukup waktu untuk konvensi untuk mengkristal, tetapi kami memiliki contoh .NET Framework Class Libraries sendiri. Pertimbangkan bahwa ketika metode LINQ ditambahkan ke .NET, mereka tidak ditambahkan langsung ke IEnumerable, tetapi sebagai metode ekstensi.

Apa yang bisa kita ambil dari ini? LINQ adalah

  1. satu set fungsionalitas kohesif yang tidak selalu diperlukan,
  2. dimaksudkan untuk digunakan dalam gaya metode-rantai (misalnya, A.B().C()bukan C(B(A))), dan
  3. tidak memerlukan akses ke status pribadi objek

Jika Anda memiliki ketiga fitur tersebut, metode ekstensi sangat masuk akal. Bahkan, saya berpendapat bahwa jika satu set metode memiliki fitur # 3 dan salah satu dari dua fitur pertama, Anda harus mempertimbangkan membuat mereka metode ekstensi.

Metode ekstensi:

  1. Memungkinkan Anda untuk menghindari mencemari API inti suatu kelas,
  2. Tidak hanya memungkinkan gaya metode-rantai, tetapi memungkinkannya untuk lebih fleksibel menangani nullnilai karena metode ekstensi yang dipanggil dari nullnilai hanya menerima nullsebagai argumen pertama,
  3. Mencegah Anda dari mengakses / memodifikasi keadaan pribadi / dilindungi secara tidak sengaja dengan metode yang seharusnya tidak memiliki akses ke sana

Metode ekstensi memiliki satu manfaat lagi: mereka masih dapat digunakan sebagai metode statis, dan dapat diteruskan secara langsung sebagai nilai ke fungsi urutan yang lebih tinggi. Jadi, jika MyMethodini merupakan metode ekstensi, alih-alih mengatakan myList.Select(x => x.MyMethod()), Anda bisa menggunakannya myList.Select(MyMethod). Saya menemukan itu lebih baik.

James Jensen
sumber
4
Alasan LINQ diimplementasikan sebagai seperangkat metode ekstensi adalah bahwa itu dimaksudkan untuk menjadi seperangkat operasi yang umum pada suatu antarmuka dan Anda tidak dapat menempatkan implementasi pada antarmuka. Ini tidak benar-benar ada hubungannya dengan poin Anda (1) dan (2). Ini berbeda dengan solusi Java untuk masalah yang sama, yaitu untuk memungkinkan implementasi dalam definisi antarmuka (yang membuka pintu ke dunia penyalahgunaan antarmuka).
Semut P
+ msgstr "dan Anda tidak dapat meletakkan implementasi pada antarmuka". Belum, tetapi pernyataan itu tidak lagi benar ketika C # 8 menyentuh mesin kami :)
David Arno
@ David benar-benar? Saya sudah keluar dari dunia C # untuk sementara waktu. Itu memalukan. Sepertinya langkah mundur bagi saya ...
Ant P
4

Kekuatan pendorong di belakang metode ekstensi adalah kemampuan untuk menambahkan fungsionalitas yang Anda butuhkan untuk semua kelas atau antarmuka dari jenis tertentu terlepas dari apakah mereka disegel atau dalam rakitan lain. Anda dapat melihat bukti ini dengan kode LINQ, menambahkan fungsi ke semua IEnumerable<T>objek, apakah itu daftar atau tumpukan, dll. Konsepnya adalah untuk membuktikan fungsi di masa depan sehingga jika Anda membuat sendiri, IEnumerable<T>Anda dapat secara otomatis menggunakan LINQ dengan itu.

Yang mengatakan, ada fitur bahasa yang cukup baru sehingga Anda tidak memiliki pedoman kapan harus menggunakan atau tidak menggunakannya. Namun, mereka mengaktifkan sejumlah hal yang sebelumnya tidak mungkin:

  • API Lancar (lihat Pernyataan Lancar untuk contoh cara menambahkan API lancar ke objek apa pun)
  • Mengintegrasikan fitur (NetCore Asp.NET MVC menggunakan metode ekstensi untuk menghubungkan dependensi dan fitur dalam kode startup)
  • Melokalisasi dampak fitur (contoh Anda dalam pernyataan pembuka)

Agar adil, hanya waktu yang akan mengatakan seberapa jauh terlalu jauh, atau pada titik mana metode penyuluhan menjadi penghalang dan bukan anugerah. Seperti disebutkan dalam jawaban lain, tidak ada dalam metode ekstensi yang tidak dapat dilakukan dengan metode statis. Namun, ketika digunakan dengan bijaksana mereka dapat membuat kode lebih mudah dibaca.

Bagaimana dengan sengaja menggunakan metode ekstensi di majelis yang sama?

Saya pikir ada nilai dalam memiliki fungsionalitas inti yang teruji dan dipercaya dengan baik, dan kemudian tidak mengacaukannya sampai Anda benar-benar harus melakukannya. Kode baru yang bergantung padanya, tetapi membutuhkan fungsionalitas semata-mata untuk kepentingan kode baru dapat menggunakan Metode Ekstensi untuk menambahkan fitur apa pun untuk mendukung kode baru. Fungsi-fungsi itu hanya menarik untuk kode baru, dan ruang lingkup metode ekstensi terbatas pada namespace tempat mereka dideklarasikan di dalamnya.

Saya tidak akan secara membabi buta mengesampingkan penggunaan metode ekstensi, tetapi saya akan mempertimbangkan apakah fungsionalitas baru harus benar-benar ditambahkan ke kode inti yang ada yang diuji dengan baik.

Berin Loritsch
sumber
1
+1 untuk apis yang lancar.
Robert Harvey
@RobertHarvey, Paragraf terakhir saya di bawah judul untuk "dalam majelis yang sama"? Jika Anda memiliki kode sumber untuk perakitan itu, Anda dapat melakukan apa pun yang Anda inginkan dengannya, tetapi setelah dikompilasi, kode tersebut disegel.
Berin Loritsch
Menghapus komentar saya, karena saya tampaknya tidak mengkomunikasikan poin saya secara efektif.
Robert Harvey
1

Apakah ada pola atau praktik terbaik yang ditetapkan saat menggunakan metode ekstensi?

Salah satu alasan paling umum untuk menggunakan metode ekstensi adalah sebagai sarana untuk "memperluas, tidak memodifikasi" antarmuka. Daripada memiliki IFoodan IFoo2, di mana yang terakhir memperkenalkan metode baru Bar, Barditambahkan ke IFoomelalui metode ekstensi.

Salah satu alasan Linq menggunakan metode ekstensi untuk Where, Selectdll adalah untuk memungkinkan pengguna untuk menentukan versi mereka sendiri dari metode ini, yang kemudian digunakan oleh sintaks permintaan Linq juga.

Di luar itu, Pendekatan yang saya gunakan, yang tampaknya sesuai dengan apa yang biasanya saya lihat dalam .NET framework setidaknya adalah:

  1. Masukkan metode yang terkait langsung dengan kelas di dalam kelas itu sendiri,
  2. Masukkan semua metode yang tidak terkait dengan fungsionalitas inti kelas dalam metode ekstensi.

Jadi jika saya memiliki Option<T>, saya akan menempatkan hal-hal seperti HasValue, Value, ==, dll dalam jenis itu sendiri. Tapi sesuatu seperti Mapfungsi, yang mengubah monad dari T1menjadi T2, saya memasukkan metode ekstensi:

public static Option<TOutput> Map<TInput, TOutput>(this Option<TInput> input, 
                                                   Func<TInput, TOutput> f) => ...
David Arno
sumber
1
One of the reasons Linq uses extension methods for Where, Select etc is in order to allow users to define their own versions of these methods, which are then used by the Linq query syntax too. -- Apa kamu yakin akan hal itu? Sintaks Linq banyak menggunakan ekspresi lambda (yaitu fungsi kelas satu), sehingga tidak perlu menulis versi Selectdan Where. Saya tahu tidak ada kasus di mana orang memutar versi mereka sendiri dari fungsi-fungsi ini.
Robert Harvey
Yang mengatakan, saya menemukan jawaban Anda yang paling menarik dari semua jawaban sejauh ini. Metode ekstensi adalah cara yang bagus untuk mengembangkan antarmuka, itulah sebabnya mereka diciptakan untuk Linq.
Robert Harvey
@RobertHarvey, Anda tidak harus, tetapi Anda bisa .
David Arno
@RobertHarvey Jika Anda beralih ke if (!predicate(item))kode itu if (predicate(item)), hasilnya berubah karena menggunakan versi saya Where.
David Arno
Ya saya mengerti. Tetapi tidak ada yang akan melakukan ini; mereka hanya akan mengubah predikat. Itulah inti dari predikat: ubah kondisi tanpa mengubah fungsinya.
Robert Harvey