Haruskah Anda mendeklarasikan metode menggunakan kelebihan beban atau parameter opsional di C # 4.0?

94

Saya menonton pembicaraan Anders tentang C # 4.0 dan pratinjau diam-diam C # 5.0 , dan itu membuat saya berpikir tentang kapan parameter opsional tersedia di C # apa yang akan menjadi cara yang disarankan untuk menyatakan metode yang tidak memerlukan semua parameter yang ditentukan?

Misalnya sesuatu seperti FileStreamkelas memiliki sekitar lima belas konstruktor berbeda yang dapat dibagi menjadi 'keluarga' logis misalnya yang di bawah dari sebuah string, yang dari an IntPtrdan yang dari a SafeFileHandle.

FileStream(string,FileMode);
FileStream(string,FileMode,FileAccess);
FileStream(string,FileMode,FileAccess,FileShare);
FileStream(string,FileMode,FileAccess,FileShare,int);
FileStream(string,FileMode,FileAccess,FileShare,int,bool);

Tampak bagi saya bahwa jenis pola ini dapat disederhanakan dengan memiliki tiga konstruktor, dan menggunakan parameter opsional untuk salah satu yang dapat default, yang akan membuat keluarga konstruktor yang berbeda lebih berbeda [catatan: Saya tahu perubahan ini tidak akan terjadi dibuat di BCL, saya sedang berbicara hipotetis untuk jenis situasi ini].

Bagaimana menurut anda? Dari C # 4.0 akan lebih masuk akal untuk membuat kelompok konstruktor dan metode yang terkait erat sebagai metode tunggal dengan parameter opsional, atau adakah alasan yang baik untuk tetap menggunakan mekanisme banyak-kelebihan tradisional?

Greg Beech
sumber

Jawaban:

122

Saya akan mempertimbangkan yang berikut:

  • Apakah Anda memerlukan kode Anda untuk digunakan dari bahasa yang tidak mendukung parameter opsional? Jika demikian, pertimbangkan untuk menyertakan kelebihan beban.
  • Apakah Anda memiliki anggota di tim Anda yang dengan keras menentang parameter opsional? (Terkadang lebih mudah untuk hidup dengan keputusan yang tidak Anda sukai daripada memperdebatkan kasus ini.)
  • Apakah Anda yakin bahwa default Anda tidak akan berubah di antara build kode Anda, atau jika mungkin, apakah penelepon Anda akan setuju dengan itu?

Saya belum memeriksa bagaimana default akan bekerja, tapi saya berasumsi bahwa nilai default akan dimasukkan ke dalam kode panggilan, sama seperti referensi ke constfield. Biasanya tidak apa-apa - perubahan ke nilai default cukup signifikan - tetapi itu adalah hal yang perlu dipertimbangkan.

Jon Skeet
sumber
21
1 untuk kebijaksanaan pragmatisme: Terkadang lebih mudah untuk hidup dengan keputusan yang tidak Anda sukai daripada memperdebatkan kasus tersebut.
legends2k
13
@romkyns: Tidak, efek overload tidak sama dengan poin 3. Dengan kelebihan beban menyediakan default, defaultnya ada dalam kode perpustakaan - jadi jika Anda mengubah default dan memberikan versi baru perpustakaan, pemanggil akan lihat default baru tanpa kompilasi ulang. Sedangkan dengan parameter opsional, Anda perlu mengkompilasi ulang untuk "melihat" default baru. Sering kali, ini bukanlah perbedaan yang penting, tetapi itu adalah perbedaan.
Jon Skeet
hai @ JonSkeet, saya ingin tahu apakah kita menggunakan kedua yaitu fungsi dengan parameter opsional dan lainnya dengan overloading metode mana yang akan dipanggil ?? misalnya Tambah (int a, int b) dan Tambah (int a, int b, int c = 0) dan pemanggilan fungsi katakan: Tambah (5,10); metode mana yang akan disebut fungsi kelebihan beban atau fungsi parameter opsional? terima kasih :)
SHEKHAR SHETE
@ Shekshar: Sudahkah Anda mencobanya? Baca spesifikasi untuk detailnya, tetapi pada dasarnya dalam tie-breaker, metode di mana kompilator tidak perlu mengisi parameter opsional apa pun yang menang.
Jon Skeet
@JonSkeet baru saja saya coba dengan di atas ... fungsi overloading menang atas parameter opsional :)
SHEKHAR SHETE
19

Ketika sebuah metode kelebihan beban biasanya melakukan hal yang sama dengan jumlah argumen yang berbeda, maka default akan digunakan.

Jika metode overload menjalankan fungsi secara berbeda berdasarkan parameternya, maka overloading akan terus digunakan.

Saya menggunakan opsional kembali di hari-hari VB6 saya dan sejak itu melewatkannya, itu akan mengurangi banyak duplikasi komentar XML di C #.

cfeduke
sumber
11

Saya telah menggunakan Delphi dengan parameter opsional selamanya. Saya telah beralih menggunakan kelebihan beban sebagai gantinya.

Karena ketika Anda membuat lebih banyak kelebihan, Anda akan selalu berkonflik dengan formulir parameter opsional, dan kemudian Anda harus mengubahnya menjadi non-opsional.

Dan saya suka anggapan bahwa umumnya ada satu metode super , dan sisanya adalah pembungkus sederhana di sekitarnya.

Ian Boyd
sumber
1
Saya sangat setuju dengan ini, namun ada peringatan bahwa ketika Anda memiliki metode yang mengambil beberapa (3+) parameter, yang pada dasarnya semua "opsional" (dapat diganti dengan default) Anda bisa berakhir dengan banyak permutasi dari tanda tangan metode tanpa manfaat lagi. Pertimbangkan Foo(A, B, C)membutuhkan Foo(A), Foo(B), Foo(C), Foo(A, B), Foo(A, C), Foo(B, C).
Dan Lugg
7

Saya pasti akan menggunakan fitur parameter opsional 4.0. Itu menghilangkan konyol ...

public void M1( string foo, string bar )
{
   // do that thang
}

public void M1( string foo )
{
  M1( foo, "bar default" ); // I have always hated this line of code specifically
}

... dan meletakkan nilainya tepat di tempat yang dapat dilihat penelepon ...

public void M1( string foo, string bar = "bar default" )
{
   // do that thang
}

Jauh lebih sederhana dan jauh lebih sedikit rawan kesalahan. Saya sebenarnya telah melihat ini sebagai bug dalam kasus kelebihan beban ...

public void M1( string foo )
{
   M2( foo, "bar default" );  // oops!  I meant M1!
}

Saya belum bermain dengan compier 4.0, tetapi saya tidak akan terkejut mengetahui bahwa compier hanya mengeluarkan kelebihan beban untuk Anda.

JP Alioto
sumber
6

Parameter opsional pada dasarnya adalah bagian dari metadata yang mengarahkan kompiler yang memproses panggilan metode untuk memasukkan default yang sesuai di situs panggilan. Sebaliknya, beban berlebih menyediakan sarana yang digunakan kompilator untuk memilih salah satu dari sejumlah metode, beberapa di antaranya mungkin menyediakan nilai default sendiri. Perhatikan bahwa jika seseorang mencoba memanggil metode yang menetapkan parameter opsional dari kode yang ditulis dalam bahasa yang tidak mendukungnya, compiler akan mengharuskan parameter "opsional" ditentukan, tetapi karena memanggil metode tanpa menentukan parameter opsional adalah setara dengan memanggilnya dengan parameter yang sama dengan nilai default, tidak ada kendala untuk bahasa seperti itu memanggil metode tersebut.

Konsekuensi signifikan dari pengikatan parameter opsional di situs panggilan adalah bahwa parameter tersebut akan diberi nilai berdasarkan versi kode target yang tersedia untuk kompilator. Jika sebuah assembly Foomemiliki metode Boo(int)dengan nilai default 5, dan assembly Barberisi panggilan ke Foo.Boo(), kompilator akan memprosesnya sebagai file Foo.Boo(5). Jika nilai default diubah menjadi 6 dan perakitan Foodikompilasi ulang, Barakan terus memanggil Foo.Boo(5)kecuali atau sampai itu dikompilasi ulang dengan versi baru Foo. Oleh karena itu, seseorang harus menghindari penggunaan parameter opsional untuk hal-hal yang mungkin berubah.

supercat
sumber
Re: "Karena itu, seseorang harus menghindari penggunaan parameter opsional untuk hal-hal yang mungkin berubah." Saya setuju bahwa ini dapat menjadi masalah jika perubahan tidak diketahui oleh kode klien. Namun, masalah yang sama terjadi ketika nilai default tersembunyi di dalam sebuah metode yang berlebihan: void Foo(int value) … void Foo() { Foo(42); }. Dari luar, pemanggil tidak tahu nilai default apa yang digunakan, atau kapan nilai itu mungkin berubah; seseorang harus memantau dokumentasi tertulis untuk itu. Nilai default untuk parameter opsional dapat dilihat seperti itu: dokumentasi-dalam-kode apa nilai defaultnya.
stakx - tidak lagi berkontribusi pada
@stakx: Jika overload tanpa parameter chain menjadi overload dengan parameter, mengubah nilai "default" dari parameter tersebut dan mengompilasi ulang definisi overload akan mengubah nilai yang digunakannya meskipun kode panggilan tidak dikompilasi ulang .
supercat
Benar tapi itu tidak membuatnya lebih bermasalah daripada alternatifnya. Dalam satu kasus (kelebihan metode), kode panggilan tidak memiliki suara dalam nilai default. Ini bisa sesuai jika kode panggilan benar-benar tidak peduli sama sekali tentang parameter opsional dan artinya. Dalam kasus lain (parameter opsional dengan nilai default), kode panggilan yang dikompilasi sebelumnya tidak akan terpengaruh ketika nilai default berubah. Ini juga dapat dilakukan jika kode panggilan benar-benar peduli tentang parameter; menghilangkannya di source sama seperti mengatakan, "nilai default yang disarankan saat ini OK untuk saya."
stakx - tidak lagi berkontribusi pada
Hal yang ingin saya sampaikan di sini adalah bahwa meskipun ada konsekuensi untuk salah satu pendekatan (seperti yang Anda tunjukkan), mereka tidak secara inheren menguntungkan atau tidak menguntungkan. Itu juga tergantung pada kebutuhan dan tujuan kode panggilan. Dari POV tersebut, saya menemukan vonis di kalimat terakhir jawaban Anda agak terlalu kaku.
stakx - tidak lagi berkontribusi pada
@ stakx: Saya mengatakan "hindari menggunakan" daripada "tidak pernah menggunakan". Jika mengubah X akan berarti bahwa rekompilasi Y berikutnya akan mengubah perilaku Y, yang memerlukan konfigurasi sistem build sehingga setiap kompilasi ulang X juga mengompilasi ulang Y (memperlambat semuanya), atau menimbulkan risiko programmer akan berubah X dengan cara yang akan merusak Y saat dikompilasi, dan hanya menemukan kerusakan tersebut di lain waktu saat Y diubah karena alasan yang sama sekali tidak terkait. Parameter default sebaiknya hanya digunakan jika keuntungannya lebih besar daripada biayanya.
supercat
4

Dapat diperdebatkan apakah argumen opsional atau kelebihan beban harus digunakan atau tidak, tetapi yang terpenting, masing-masing memiliki area sendiri di mana mereka tidak tergantikan.

Argumen opsional, saat digunakan dalam kombinasi dengan argumen bernama, sangat berguna saat dikombinasikan dengan beberapa panggilan COM yang panjang-argumen-daftar-dengan-semua-opsional.

Overload sangat berguna ketika metode dapat beroperasi pada banyak tipe argumen yang berbeda (hanya satu dari contoh), dan melakukan pengecoran secara internal, misalnya; Anda cukup memberinya makan dengan tipe data apa pun yang masuk akal (yang diterima oleh beberapa kelebihan yang ada). Tidak bisa mengalahkan itu dengan argumen opsional.

mr.b.
sumber
3

Saya menantikan parameter opsional karena itu membuat default lebih dekat dengan metode. Jadi, alih-alih lusinan baris untuk kelebihan beban yang hanya memanggil metode "diperluas", Anda cukup menetapkan metode satu kali dan Anda dapat melihat parameter opsional apa yang digunakan secara default di tanda tangan metode. Saya lebih suka melihat:

public Rectangle (Point start = Point.Zero, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

Dari pada ini:

public Rectangle (Point start, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

public Rectangle (int width, int height) :
    this (Point.Zero, width, height)
{
}

Jelas contoh ini sangat sederhana tetapi kasus di OP dengan 5 kelebihan beban, semuanya bisa menjadi sangat cepat.

Mark A. Nicolosi
sumber
7
Saya pernah mendengar parameter opsional harus menjadi yang terakhir, bukan?
Ilya Ryzhenkov
Tergantung pada desain Anda. Mungkin argumen 'mulai' biasanya penting, kecuali jika tidak. Mungkin Anda memiliki tanda tangan yang sama di tempat lain, artinya sesuatu yang berbeda. Untuk contoh yang dibuat-buat, Public Rectangle (int width, int height, Point innerSquareStart, Point innerSquareEnd) {}
Robert P
13
Dari apa yang mereka katakan dalam pembicaraan, parameter opsional harus setelah parameter yang diperlukan.
Greg Beech
3

Salah satu aspek favorit saya dari parameter opsional adalah Anda melihat apa yang terjadi pada parameter Anda jika Anda tidak menyediakannya, bahkan tanpa membuka definisi metode. Visual Studio hanya akan menunjukkan nilai default untuk parameter saat Anda mengetik nama metode. Dengan metode kelebihan beban, Anda tidak dapat membaca dokumentasi (bahkan jika tersedia) atau dengan langsung menavigasi ke definisi metode (jika tersedia) dan ke metode yang dibungkus oleh kelebihan beban.

Secara khusus: upaya dokumentasi dapat meningkat pesat dengan jumlah kelebihan muatan, dan Anda mungkin akan menyalin komentar yang sudah ada dari kelebihan muatan yang ada. Ini cukup mengganggu, karena tidak menghasilkan nilai apa pun dan melanggar prinsip KERING ). Di sisi lain, dengan parameter opsional, terdapat tepat satu tempat di mana semua parameter didokumentasikan dan Anda akan melihat artinya serta nilai defaultnya saat mengetik.

Last but not least, jika Anda adalah konsumen API, Anda bahkan mungkin tidak memiliki opsi untuk memeriksa detail implementasi (jika Anda tidak memiliki kode sumber) dan oleh karena itu tidak memiliki kesempatan untuk melihat metode super mana yang kelebihan beban sedang membungkus. Jadi Anda terjebak dengan membaca dokumen dan berharap bahwa semua nilai default terdaftar di sana, tetapi ini tidak selalu terjadi.

Tentu saja, ini bukan jawaban yang menangani semua aspek, tapi saya rasa ini menambahkan satu jawaban yang belum dibahas sejauh ini.

HimBromBeere
sumber
1

Meskipun keduanya (seharusnya?) Adalah dua cara yang setara secara konseptual yang tersedia bagi Anda untuk membuat model API dari awal, sayangnya keduanya memiliki beberapa perbedaan halus saat Anda perlu mempertimbangkan kompatibilitas runtime ke belakang untuk klien lama Anda di alam liar. Rekan saya (terima kasih Brent!) Mengarahkan saya ke posting yang luar biasa ini : Masalah versi dengan argumen opsional . Beberapa kutipan darinya:

Alasan pertama mengapa parameter opsional diperkenalkan ke C # 4 adalah untuk mendukung interop COM. Itu dia. Dan sekarang, kami belajar tentang implikasi penuh dari fakta ini. Jika Anda memiliki metode dengan parameter opsional, Anda tidak akan pernah dapat menambahkan kelebihan beban dengan parameter opsional tambahan karena takut menyebabkan perubahan pemutusan waktu kompilasi. Dan Anda tidak akan pernah bisa menghapus kelebihan beban yang ada, karena ini selalu menjadi perubahan yang merusak waktu proses. Anda perlu memperlakukannya seperti antarmuka. Satu-satunya jalan keluar Anda dalam kasus ini adalah menulis metode baru dengan nama baru. Jadi perhatikan ini jika Anda berencana untuk menggunakan argumen opsional di API Anda.

RayLuo
sumber
1

Satu peringatan dari parameter opsional adalah pembuatan versi, di mana refactor memiliki konsekuensi yang tidak diinginkan. Sebuah contoh:

Kode awal

public string HandleError(string message, bool silent=true, bool isCritical=true)
{
  ...
}

Asumsikan ini adalah salah satu dari banyak pemanggil metode di atas:

HandleError("Disk is full", false);

Di sini kejadiannya tidak diam dan diperlakukan sebagai kritis.

Sekarang katakanlah setelah refactor kita menemukan bahwa semua error meminta pengguna, jadi kita tidak lagi membutuhkan flag silent. Jadi kami menghapusnya.

Setelah refactor

Panggilan sebelumnya masih terkompilasi, dan katakanlah itu lolos dari refactor tidak berubah:

public string HandleError(string message, /*bool silent=true,*/ bool isCritical=true)
{
  ...
}

...

// Some other distant code file:
HandleError("Disk is full", false);

Sekarang falseakan memiliki efek yang tidak diinginkan, acara tersebut tidak lagi dianggap kritis.

Hal ini dapat mengakibatkan kerusakan yang tidak kentara, karena tidak akan ada kesalahan kompilasi atau runtime (tidak seperti beberapa peringatan opsional lainnya, seperti ini atau ini ).

Perhatikan bahwa ada banyak bentuk dari masalah yang sama ini. Satu bentuk lain diuraikan di sini .

Perhatikan juga bahwa secara ketat menggunakan parameter bernama saat memanggil metode akan menghindari masalah ini, seperti seperti ini: HandleError("Disk is full", silent:false). Namun, mungkin tidak praktis untuk mengasumsikan bahwa semua pengembang lain (atau pengguna API publik) akan melakukannya.

Untuk alasan ini saya akan menghindari penggunaan parameter opsional di API publik (atau bahkan metode publik jika mungkin digunakan secara luas) kecuali ada pertimbangan lain yang menarik.

Zach
sumber
0

Kedua parameter Opsional, Kelebihan metode memiliki kelebihan atau kekurangannya sendiri. Itu tergantung pada preferensi Anda untuk memilih di antara keduanya.

Parameter Opsional: hanya tersedia di .Net 4.0. parameter opsional mengurangi ukuran kode Anda. Anda tidak dapat menentukan parameter dan ref

metode kelebihan beban: Anda dapat Menentukan parameter Keluar dan ref. Ukuran kode akan meningkat tetapi metode yang kelebihan beban mudah dipahami.

sushil pandey
sumber
0

Dalam banyak kasus, parameter opsional digunakan untuk mengalihkan eksekusi. Sebagai contoh:

decimal GetPrice(string productName, decimal discountPercentage = 0)
{

    decimal basePrice = CalculateBasePrice(productName);

    if (discountPercentage > 0)
        return basePrice * (1 - discountPercentage / 100);
    else
        return basePrice;
}

Parameter diskon di sini digunakan untuk memberi makan pernyataan if-then-else. Ada polimorfisme yang tidak dikenali, dan kemudian diimplementasikan sebagai pernyataan if-then-else. Dalam kasus seperti itu, jauh lebih baik untuk membagi dua aliran kontrol menjadi dua metode independen:

decimal GetPrice(string productName)
{
    decimal basePrice = CalculateBasePrice(productName);
    return basePrice;
}

decimal GetPrice(string productName, decimal discountPercentage)
{

    if (discountPercentage <= 0)
        throw new ArgumentException();

    decimal basePrice = GetPrice(productName);

    decimal discountedPrice = basePrice * (1 - discountPercentage / 100);

    return discountedPrice;

}

Dengan cara ini, kami bahkan telah melindungi kelas dari menerima panggilan dengan diskon nol. Panggilan tersebut berarti bahwa penelepon berpikir bahwa ada diskon, tetapi sebenarnya tidak ada diskon sama sekali. Kesalahpahaman seperti itu dapat dengan mudah menyebabkan bug.

Dalam kasus seperti ini, saya memilih untuk tidak memiliki parameter opsional, tetapi untuk memaksa pemanggil secara eksplisit memilih skenario eksekusi yang sesuai dengan situasi saat ini.

Situasinya sangat mirip dengan memiliki parameter yang bisa nol. Itu ide yang sama buruknya ketika implementasi bermuara pada pernyataan seperti if (x == null).

Anda dapat menemukan analisis mendetail pada tautan ini: Menghindari Parameter Opsional dan Menghindari Parameter Nol

Zoran Horvat
sumber
0

Untuk menambahkan no-brainer kapan harus menggunakan overload dan bukan opsional:

Kapanpun Anda memiliki sejumlah parameter yang hanya masuk akal jika digabungkan, jangan masukkan pilihan pada mereka.

Atau lebih umum lagi, setiap kali tanda tangan metode Anda mengaktifkan pola penggunaan yang tidak masuk akal, batasi jumlah permutasi panggilan yang mungkin. Misalnya, dengan menggunakan kelebihan beban, bukan opsional (aturan ini juga berlaku jika Anda memiliki beberapa parameter dari tipe data yang sama, omong-omong; di sini, perangkat seperti metode pabrik atau tipe data khusus dapat membantu).

Contoh:

enum Match {
    Regex,
    Wildcard,
    ContainsString,
}

// Don't: This way, Enumerate() can be called in a way
//         which does not make sense:
IEnumerable<string> Enumerate(string searchPattern = null,
                              Match match = Match.Regex,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);

// Better: Provide only overloads which cannot be mis-used:
IEnumerable<string> Enumerate(SearchOption searchOption = SearchOption.TopDirectoryOnly);
IEnumerable<string> Enumerate(string searchPattern, Match match,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);
Sebastian Mach
sumber