Jawaban lain telah memberikan penjelasan yang bagus mengapa parameter opsional tidak bisa menjadi ekspresi dinamis. Tetapi, untuk menghitung ulang, parameter default berperilaku seperti konstanta waktu kompilasi. Itu artinya kompiler harus dapat mengevaluasinya dan memberikan jawaban. Ada beberapa orang yang ingin C # menambahkan dukungan untuk kompiler yang mengevaluasi ekspresi dinamis ketika menghadapi deklarasi konstan — fitur semacam ini akan terkait dengan metode menandai "murni", tetapi itu bukan kenyataan saat ini dan mungkin tidak akan pernah terjadi.
Salah satu alternatif untuk menggunakan parameter default C # untuk metode seperti itu adalah dengan menggunakan pola yang dicontohkan oleh XmlReaderSettings
. Dalam pola ini, tentukan kelas dengan konstruktor tanpa parameter dan properti yang dapat ditulis untuk umum. Kemudian ganti semua opsi dengan default di metode Anda dengan objek jenis ini. Bahkan membuat objek ini opsional dengan menentukan default null
untuk itu. Sebagai contoh:
public class FooSettings
{
public TimeSpan Span { get; set; } = TimeSpan.FromSeconds(2);
// I imagine that if you had a heavyweight default
// thing you’d want to avoid instantiating it right away
// because the caller might override that parameter. So, be
// lazy! (Or just directly store a factory lambda with Func<IThing>).
Lazy<IThing> thing = new Lazy<IThing>(() => new FatThing());
public IThing Thing
{
get { return thing.Value; }
set { thing = new Lazy<IThing>(() => value); }
}
// Another cool thing about this pattern is that you can
// add additional optional parameters in the future without
// even breaking ABI.
//bool FutureThing { get; set; } = true;
// You can even run very complicated code to populate properties
// if you cannot use a property initialization expression.
//public FooSettings() { }
}
public class Bar
{
public void Foo(FooSettings settings = null)
{
// Allow the caller to use *all* the defaults easily.
settings = settings ?? new FooSettings();
Console.WriteLine(settings.Span);
}
}
Untuk menelepon, gunakan satu sintaks aneh itu untuk membuat instance dan menetapkan properti semua dalam satu ekspresi:
bar.Foo(); // 00:00:02
bar.Foo(new FooSettings { Span = TimeSpan.FromDays(1), }); // 1.00:00:00
bar.Foo(new FooSettings { Thing = new MyCustomThing(), }); // 00:00:02
Kerugian
Ini adalah pendekatan yang sangat berat untuk menyelesaikan masalah ini. Jika Anda menulis antarmuka internal yang cepat dan kotor dan membuat TimeSpan
nullable dan memperlakukan null seperti nilai default yang Anda inginkan akan berfungsi dengan baik, lakukan itu.
Juga, jika Anda memiliki sejumlah besar parameter atau memanggil metode dalam loop ketat, ini akan memiliki overhead instantiations kelas. Tentu saja, jika memanggil metode seperti itu dalam loop ketat, itu mungkin alami dan bahkan sangat mudah untuk menggunakan kembali instance FooSettings
objek.
Manfaat
Seperti yang saya sebutkan dalam komentar di contoh, saya pikir pola ini bagus untuk API publik. Menambahkan properti baru ke kelas adalah perubahan ABI yang tidak terputus-putus, sehingga Anda dapat menambahkan parameter opsional baru tanpa mengubah tanda tangan metode Anda menggunakan pola ini — memberikan lebih banyak kode yang dikompilasi baru-baru ini lebih banyak pilihan sambil terus mendukung kode yang dikompilasi lama tanpa kerja ekstra .
Selain itu, karena parameter metode bawaan bawaan C # diperlakukan sebagai konstanta waktu-kompas dan dimasukkan ke dalam lokasi panggilan, parameter default hanya akan digunakan oleh kode setelah dikompilasi ulang. Dengan membuat instance objek pengaturan, pemanggil secara dinamis memuat nilai default saat memanggil metode Anda. Ini berarti bahwa Anda dapat memperbarui default dengan hanya mengubah kelas pengaturan Anda. Dengan demikian, pola ini memungkinkan Anda mengubah nilai default tanpa harus mengkompilasi ulang penelepon untuk melihat nilai-nilai baru, jika itu diinginkan.
new TimeSpan(2000)
itu tidak berarti 2000 milidetik, itu berarti 2000 "kutu" yang merupakan 0,2 milidetik, atau satu per sepuluh dari dua detik.