Apakah ada pendekatan yang masuk akal untuk parameter tipe "default" di C # Generics?

93

Dalam template C ++, seseorang dapat menentukan bahwa parameter tipe tertentu adalah default. Yaitu kecuali ditentukan secara eksplisit, itu akan menggunakan tipe T.

Bisakah ini dilakukan atau diperkirakan di C #?

Saya mencari sesuatu seperti:

public class MyTemplate<T1, T2=string> {}

Sehingga contoh dari tipe yang tidak secara eksplisit menentukan T2:

MyTemplate<int> t = new MyTemplate<int>();

Pada dasarnya:

MyTemplate<int, string> t = new MyTemplate<int, string>();

Pada akhirnya saya melihat kasus di mana ada template yang cukup banyak digunakan, tetapi saya sedang mempertimbangkan untuk memperluas dengan parameter tipe tambahan. Saya bisa membuat subkelas, saya rasa, tapi saya penasaran apakah ada pilihan lain dalam hal ini.

el2iot2
sumber

Jawaban:

77

Subclassing adalah opsi terbaik.

Saya akan membuat subclass kelas generik utama Anda:

class BaseGeneric<T,U>

dengan kelas tertentu

class MyGeneric<T> : BaseGeneric<T, string>

Ini membuatnya mudah untuk menyimpan logika Anda di satu tempat (kelas dasar), tetapi juga mudah untuk menyediakan kedua opsi penggunaan. Bergantung pada kelasnya, mungkin hanya ada sedikit pekerjaan tambahan yang diperlukan untuk mewujudkannya.

Reed Copsey
sumber
1
ah ... itu masuk akal. Apakah nama tipe boleh sama jika parameter tipe memberikan tanda tangan unik?
el2iot2
2
@ee: ya, obat generik overloadableberdasarkan jumlah parameter.
Mehrdad Afshari
@ee: Ya, tapi saya akan berhati-hati melakukan itu. Hal itu "legal" di .NET untuk melakukannya, tetapi dapat menyebabkan kebingungan. Saya lebih suka nama tipe turunan string mirip dengan kelas generik utama (jadi jelas apa itu / mudah ditemukan), tetapi nama yang membuatnya jelas bahwa itu adalah string.
Reed Copsey
@Reed: Apakah itu benar-benar menyebabkan kebingungan? Untuk kasus khusus ini, saya pikir memiliki nama yang sama bahkan membantu. Ada contoh di .NET yang melakukan hal yang sama: Func <> delegate misalnya.
Mehrdad Afshari
4
Misalnya, Predikat <T> hanyalah Func <T, bool>, tetapi diganti namanya karena untuk tujuan yang berbeda.
Reed Copsey
19

Salah satu solusinya adalah subclassing. Satu lagi yang akan saya gunakan sebagai gantinya, adalah metode pabrik (digabungkan dengan kata kunci var).

public class MyTemplate<T1,T2>
{
     public MyTemplate(..args..) { ... } // constructor
}

public static class MyTemplate{

     public static MyTemplate<T1,T2> Create<T1,T2>(..args..)
     {
         return new MyTemplate<T1, T2>(... params ...);
     }

     public static MyTemplate<T1, string> Create<T1>(...args...)
     {
         return new MyTemplate<T1, string>(... params ...);
     }
}

var val1 = MyTemplate.Create<int,decimal>();
var val2 = MyTemplate.Create<int>();

Dalam contoh di atas val2adalah tipe MyTemplate<int,string> dan bukan tipe yang diturunkan darinya.

Tipe class MyStringTemplate<T>:MyTemplate<T,string>bukan tipe yang sama dengan MyTemplate<T,string>. Ini bisa menimbulkan beberapa masalah dalam skenario tertentu. Misalnya, Anda tidak dapat mentransmisikan instance MyTemplate<T,string>ke MyStringTemplate<T>.

Thanasis Ioannidis
sumber
3
Ini adalah pendekatan yang paling berguna. Solusi yang sangat bagus
T-moty
12

Anda juga dapat membuat kelas Overload seperti itu

public class MyTemplate<T1, T2> {
    public T1 Prop1 { get; set; }
    public T2 Prop2 { get; set; }
}

public class MyTemplate<T1> : MyTemplate<T1, string>{}
Nerdroid
sumber
Harap baca jawaban lain sebelum Anda memposting jawaban yang terlambat, karena mungkin solusi Anda sama dengan yang lain.
Cheng Chen
7
jawaban yang diterima adalah membuat kelas dengan nama berbeda, solusi saya adalah
Membebani
2
Tidak, keduanya membuat kelas baru. Namanya tidak penting di sini. MyTemplate<T1>adalah kelas yang berbeda dari MyTemplate<T1, T2>, tidak keduanya AnotherTemplate<T1>.
Cheng Chen
7
Meskipun tipe sebenarnya berbeda, yang jelas dan benar, pengkodean lebih mudah jika Anda tetap menggunakan nama yang sama. Tidak jelas bagi semua orang bahwa class "overload" dapat digunakan dengan cara itu. @DannyChen benar - dari segi teknis hasilnya sama, tapi jawaban ini lebih mendekati untuk mencapai apa yang diminta OP.
Kuba
1
Sayangnya solusi ini masih kalah dengan argumen generik "default" yang sebenarnya, karena a MyTemplate<T1, string>tidak dapat ditetapkan MyTemplate<T1>, yang dapat diinginkan
Felk
10

C # tidak mendukung fitur seperti itu.

Seperti yang Anda katakan, Anda dapat membuat subkelasnya (jika tidak disegel, dan menduplikasi semua deklarasi konstruktor) tetapi itu adalah hal yang sama sekali berbeda.

Mehrdad Afshari
sumber
3

Sayangnya C # tidak mendukung apa yang Anda coba lakukan. Ini akan menjadi fitur yang sulit untuk diterapkan mengingat tipe default untuk parameter harus mematuhi batasan umum dan kemungkinan besar akan membuat pusing saat CLR mencoba memastikan keamanan tipe.

Andrew Hare
sumber
1
Tidak juga. Itu bisa saja dilakukan dengan atribut (seperti parameter default di VB.NET) dan meminta kompilator menggantinya pada waktu kompilasi. Alasan utamanya adalah tujuan desain C #.
Mehrdad Afshari
Kompilator harus memastikan bahwa parameter default memenuhi batasan generik. Juga parameter default akan menjadi batasan umum itu sendiri karena asumsi apa pun yang dibuat tentang parameter tipe dalam metode akan mengharuskan semua parameter tipe non-default mewarisi darinya.
Andrew Hare
@ Andrew, parameter default tidak perlu menjadi batasan umum. Jika ini berperilaku lebih seperti parameter template default di C ++ kemudian memperluas kelas automatonic itu akan baik-baik saja dilakukan: MyTemplate <int, float> x = null Karena T2 tidak memiliki batasan umum, jadi float baik-baik saja meskipun tipe string default . Dengan cara ini, parameter templat default pada dasarnya hanyalah "gula sintaksis" untuk menulis MyTemplate <int> sebagai singkatan dari MyTemplate <int, string>.
Tyler Laing
@ Andrew, saya setuju bahwa compiler C # harus memverifikasi bahwa parameter template default memenuhi batasan umum yang ada. Kompilator C # sudah memverifikasi sesuatu yang serupa dalam deklarasi kelas, misalnya menambahkan batasan umum seperti "where U: ISomeInterface" ke kelas BaseGeneric <T, U> Reed dan kemudian MyGeneric <T> akan gagal untuk dikompilasi dengan kesalahan. Memverifikasi parameter template default hampir sama: compiler memverifikasi deklarasi kelas dan pesan kesalahan bisa sama atau sangat mirip.
Tyler Laing
"Ini akan menjadi fitur yang sulit untuk diterapkan" Itu tidak masuk akal. Ini akan menjadi hal yang sepele untuk diterapkan. Pekerjaan memvalidasi tipe terhadap batasan template tidak menjadi lebih sulit karena tipe kandidat muncul di tempat yang berbeda dalam file (yaitu di template dan bukan di instantiation template).
Lumpur