Apa cara terbaik untuk memanggil metode generik ketika parameter tipe tidak diketahui pada waktu kompilasi, tetapi sebaliknya diperoleh secara dinamis saat runtime?
Pertimbangkan kode contoh berikut - di dalam Example()
metode, apa cara paling ringkas untuk memohon GenericMethod<T>()
menggunakan yang Type
disimpan dalam myType
variabel?
public class Sample
{
public void Example(string typeName)
{
Type myType = FindType(typeName);
// What goes here to call GenericMethod<T>()?
GenericMethod<myType>(); // This doesn't work
// What changes to call StaticMethod<T>()?
Sample.StaticMethod<myType>(); // This also doesn't work
}
public void GenericMethod<T>()
{
// ...
}
public static void StaticMethod<T>()
{
//...
}
}
c#
.net
generics
reflection
Bevan
sumber
sumber
BindingFlags.Instance
, bukan hanyaBindingFlags.NonPublic
, untuk mendapatkan metode pribadi / internal.Jawaban:
Anda perlu menggunakan refleksi untuk memulai metode, kemudian "buat" dengan memberikan argumen tipe dengan MakeGenericMethod :
Untuk metode statis, berikan
null
argumen pertama keInvoke
. Itu tidak ada hubungannya dengan metode generik - itu hanya refleksi normal.Seperti yang disebutkan, banyak dari ini lebih sederhana pada penggunaan C # 4
dynamic
- jika Anda dapat menggunakan inferensi tipe, tentu saja. Itu tidak membantu dalam kasus di mana inferensi tipe tidak tersedia, seperti contoh yang tepat dalam pertanyaan.sumber
GetMethod()
hanya menganggap metode instance publik secara default, jadi Anda mungkin perluBindingFlags.Static
dan / atauBindingFlags.NonPublic
.BindingFlags.NonPublic | BindingFlags.Instance
(dan opsionalBindingFlags.Static
).dynamic
tidak membantu karena tipe inferensi tidak tersedia. (Tidak ada argumen yang dapat digunakan kompiler untuk menentukan argumen tipe.)Hanya tambahan untuk jawaban aslinya. Sementara ini akan berhasil:
Ini juga sedikit berbahaya karena Anda kehilangan waktu kompilasi
GenericMethod
. Jika nanti Anda melakukan refactoring dan mengganti namaGenericMethod
, kode ini tidak akan melihat dan akan gagal pada saat dijalankan. Juga, jika ada post-processing dari rakitan (misalnya mengaburkan atau menghapus metode / kelas yang tidak digunakan) kode ini mungkin rusak juga.Jadi, jika Anda tahu metode yang Anda tautkan pada waktu kompilasi, dan ini tidak disebut jutaan kali jadi overhead tidak masalah, saya akan mengubah kode ini menjadi:
Meskipun tidak terlalu cantik, Anda memiliki referensi waktu kompilasi ke
GenericMethod
sini, dan jika Anda refactor, menghapus atau melakukan apa pun denganGenericMethod
, kode ini akan tetap berfungsi, atau setidaknya istirahat pada waktu kompilasi (jika misalnya Anda menghapusGenericMethod
).Cara lain untuk melakukan hal yang sama adalah dengan membuat kelas pembungkus baru, dan membuatnya melalui
Activator
. Saya tidak tahu apakah ada cara yang lebih baik.sumber
GenMethod.Method.GetGenericMethodDefinition()
bukanthis.GetType().GetMethod(GenMethod.Method.Name)
. Ini sedikit lebih bersih dan mungkin lebih aman.nameof(GenericMethod)
Memanggil metode generik dengan parameter tipe yang hanya diketahui saat runtime dapat sangat disederhanakan dengan menggunakan
dynamic
tipe alih-alih API refleksi.Untuk menggunakan teknik ini jenis harus diketahui dari objek yang sebenarnya (bukan hanya turunan dari
Type
kelas). Jika tidak, Anda harus membuat objek jenis itu atau menggunakan solusi API refleksi standar . Anda dapat membuat objek dengan menggunakan metode Activator.CreateInstance .Jika Anda ingin memanggil metode generik, bahwa dalam penggunaan "normal" akan memiliki tipe disimpulkan, maka itu hanya datang untuk casting objek tipe yang tidak dikenal
dynamic
. Ini sebuah contoh:Dan inilah output dari program ini:
Process
adalah metode instance generik yang menulis tipe sebenarnya dari argumen yang diteruskan (dengan menggunakanGetType()
metode) dan tipe parameter generik (dengan menggunakantypeof
operator).Dengan melemparkan argumen objek untuk
dynamic
mengetik, kami menunda menyediakan parameter tipe hingga runtime. KetikaProcess
metode dipanggil dengandynamic
argumen maka kompiler tidak peduli tentang jenis argumen ini. Kompiler menghasilkan kode yang pada saat runtime memeriksa jenis argumen yang dilewati (dengan menggunakan refleksi) dan memilih metode terbaik untuk memanggil. Di sini hanya ada satu metode generik ini, jadi ini dipanggil dengan parameter tipe yang tepat.Dalam contoh ini, hasilnya sama seperti jika Anda menulis:
Versi dengan tipe dinamis jelas lebih pendek dan lebih mudah untuk ditulis. Anda juga tidak perlu khawatir tentang kinerja memanggil fungsi ini beberapa kali. Panggilan berikutnya dengan argumen dari tipe yang sama harus lebih cepat berkat mekanisme caching di DLR. Tentu saja, Anda dapat menulis kode yang memanggil cache delegasi, tetapi dengan menggunakan
dynamic
tipe ini Anda mendapatkan perilaku ini secara gratis.Jika metode generik yang ingin Anda panggil tidak memiliki argumen tipe parametrized (jadi parameter tipenya tidak dapat disimpulkan) maka Anda dapat membungkus permohonan metode generik dalam metode pembantu seperti dalam contoh berikut:
Keamanan jenis meningkat
Apa yang benar-benar hebat tentang menggunakan
dynamic
objek sebagai pengganti untuk menggunakan API refleksi adalah bahwa Anda hanya kehilangan waktu kompilasi memeriksa jenis khusus ini yang Anda tidak tahu sampai runtime. Argumen lain dan nama metode dianalisis secara statis oleh kompiler seperti biasa. Jika Anda menghapus atau menambahkan lebih banyak argumen, mengubah jenisnya atau mengganti nama metode, maka Anda akan mendapatkan kesalahan waktu kompilasi. Ini tidak akan terjadi jika Anda memberikan nama metode sebagai stringType.GetMethod
dan argumen sebagai array objekMethodInfo.Invoke
.Di bawah ini adalah contoh sederhana yang menggambarkan bagaimana beberapa kesalahan dapat ditangkap pada waktu kompilasi (kode komentar) dan lainnya saat runtime. Ini juga menunjukkan bagaimana DLR mencoba menyelesaikan metode mana yang harus dihubungi.
Di sini kita kembali menjalankan beberapa metode dengan melemparkan argumen ke
dynamic
tipe. Hanya verifikasi tipe argumen pertama yang ditunda ke runtime. Anda akan mendapatkan kesalahan kompilator jika nama metode yang Anda panggil tidak ada atau jika argumen lain tidak valid (jumlah argumen yang salah atau jenis yang salah).Ketika Anda meneruskan
dynamic
argumen ke suatu metode maka panggilan ini akhir - akhir ini terikat . Metode resolusi kelebihan terjadi pada saat runtime dan mencoba untuk memilih kelebihan yang terbaik. Jadi jika Anda memanggilProcessItem
metode dengan objekBarItem
bertipe maka Anda akan benar-benar memanggil metode non-generik, karena metode ini lebih cocok untuk jenis ini. Namun, Anda akan mendapatkan kesalahan runtime ketika Anda melewati argumenAlpha
tipe karena tidak ada metode yang dapat menangani objek ini (metode generik memiliki kendalawhere T : IItem
danAlpha
kelas tidak mengimplementasikan antarmuka ini). Tapi itulah intinya. Kompiler tidak memiliki informasi bahwa panggilan ini valid. Anda sebagai seorang programmer mengetahui hal ini, dan Anda harus memastikan bahwa kode ini berjalan tanpa kesalahan.Jenis pengembalian gotcha
Ketika Anda memanggil metode non-kekosongan dengan parameter tipe dinamis, jenis kembalinya mungkin akan menjadi
dynamic
terlalu . Jadi, jika Anda ingin mengubah contoh sebelumnya ke kode ini:maka jenis objek hasil akan
dynamic
. Ini karena kompiler tidak selalu tahu metode mana yang akan dipanggil. Jika Anda mengetahui jenis pengembalian panggilan fungsi maka Anda harus secara implisit mengonversinya ke jenis yang diperlukan sehingga sisa kode diketik secara statis:Anda akan mendapatkan kesalahan runtime jika jenisnya tidak cocok.
Sebenarnya, jika Anda mencoba untuk mendapatkan nilai hasil pada contoh sebelumnya maka Anda akan mendapatkan kesalahan runtime di iterasi loop kedua. Ini karena Anda mencoba menyimpan nilai kembali dari fungsi batal.
sumber
ProcessItem
metode generik memiliki batasan generik dan hanya menerima objek yang mengimplementasikanIItem
antarmuka. Ketika Anda akan meneleponProcessItem(new Aplha(), "test" , 1);
atauProcessItem((object)(new Aplha()), "test" , 1);
Anda akan mendapatkan kesalahan kompiler tetapi ketika casting untukdynamic
Anda tunda centang itu untuk runtime.Dengan C # 4.0, refleksi tidak diperlukan karena DLR dapat menyebutnya menggunakan tipe runtime. Karena menggunakan pustaka DLR agak merepotkan secara dinamis (alih-alih kode penghasil kompiler C # untuk Anda), kerangka kerja sumber terbuka Dynamitey (.net standar 1.5) memberi Anda akses run-time cache yang mudah di-cache ke panggilan yang sama yang akan dihasilkan oleh kompiler untukmu.
sumber
Menambahkan ke jawaban Adrian Gallero :
Memanggil metode generik dari tipe info melibatkan tiga langkah.
TLDR: Memanggil metode generik yang dikenal dengan objek tipe dapat dilakukan dengan:
di mana
GenericMethod<object>
nama metode untuk memanggil dan jenis apa pun yang memenuhi batasan generik.(Aksi) cocok dengan tanda tangan dari metode yang akan dipanggil yaitu (
Func<string,string,int>
atauAction<bool>
)Langkah 1 adalah mendapatkan MethodInfo untuk definisi metode generik
Metode 1: Gunakan GetMethod () atau GetMethods () dengan jenis yang sesuai atau flag yang mengikat.
Metode 2: Buat delegasi, dapatkan objek MethodInfo dan kemudian panggil GetGenericMethodDefinition
Dari dalam kelas yang berisi metode:
Dari luar kelas yang berisi metode:
Dalam C #, nama metode, yaitu "ToString" atau "GenericMethod" sebenarnya merujuk pada sekelompok metode yang mungkin berisi satu atau lebih metode. Sampai Anda memberikan jenis parameter metode, tidak diketahui metode mana yang Anda maksud.
((Action)GenericMethod<object>)
merujuk pada delegasi untuk metode tertentu.((Func<string, int>)GenericMethod<object>)
merujuk pada kelebihan yang berbeda dari GenericMethodMetode 3: Buat ekspresi lambda yang berisi ekspresi pemanggilan metode, dapatkan objek MethodInfo dan kemudian GetGenericMethodDefinition
Ini rusak menjadi
Buat ekspresi lambda di mana tubuh adalah panggilan ke metode yang Anda inginkan.
Ekstrak tubuh dan dilemparkan ke MethodCallExpression
Dapatkan definisi metode generik dari metode tersebut
Langkah 2 memanggil MakeGenericMethod untuk membuat metode generik dengan jenis yang sesuai.
Langkah 3 adalah memanggil metode dengan argumen yang sesuai.
sumber
Tidak ada yang memberikan solusi " Refleksi klasik ", jadi di sini adalah contoh kode lengkap:
Kelas di atas
DynamicDictionaryFactory
memiliki metodeCreateDynamicGenericInstance(Type keyType, Type valueType)
dan itu menciptakan dan mengembalikan instance IDictionary, jenis kunci dan nilainya yang persis ditentukan pada panggilan
keyType
danvalueType
.Berikut ini adalah contoh lengkap cara memanggil metode ini untuk membuat instance dan menggunakan
Dictionary<String, int>
:Ketika aplikasi konsol di atas dijalankan, kami mendapatkan hasil yang benar dan diharapkan:
sumber
Ini adalah 2 sen saya berdasarkan jawaban Grax , tetapi dengan dua parameter yang diperlukan untuk metode generik.
Asumsikan metode Anda didefinisikan sebagai berikut dalam kelas Pembantu:
Dalam kasus saya, tipe U selalu merupakan koleksi yang dapat diamati yang menyimpan objek tipe T.
Karena tipe saya sudah ditentukan sebelumnya, saya pertama-tama membuat objek "dummy" yang mewakili koleksi yang dapat diamati (U) dan objek yang disimpan di dalamnya (T) dan yang akan digunakan di bawah ini untuk mendapatkan tipenya ketika memanggil Make
Kemudian panggil GetMethod untuk menemukan fungsi Generik Anda:
Sejauh ini, panggilan di atas cukup identik dengan apa yang telah dijelaskan di atas tetapi dengan perbedaan kecil ketika Anda harus melewati beberapa parameter untuk itu.
Anda perlu meneruskan larik Type [] ke fungsi MakeGenericMethod yang berisi tipe objek "dummy" yang dibuat di atas:
Setelah selesai, Anda perlu memanggil metode Invoke seperti yang disebutkan di atas.
Dan kamu sudah selesai. Mempesona!
MEMPERBARUI:
Sebagai @Bevan disorot, saya tidak perlu membuat array saat memanggil fungsi MakeGenericMethod karena dibutuhkan dalam params dan saya tidak perlu membuat objek untuk mendapatkan jenis karena saya bisa meneruskan jenis langsung ke fungsi ini. Dalam kasus saya, karena saya memiliki tipe yang telah ditentukan di kelas lain, saya hanya mengubah kode saya menjadi:
myClassInfo berisi 2 properti tipe
Type
yang saya atur di run time berdasarkan nilai enum yang diteruskan ke konstruktor dan akan memberi saya jenis yang relevan yang kemudian saya gunakan dalam MakeGenericMethod.Terima kasih lagi karena telah menyoroti @Bevan ini.
sumber
MakeGenericMethod()
memiliki kata kunci params sehingga Anda tidak perlu membuat array; Anda juga tidak perlu membuat instance untuk mendapatkan tipe -methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))
akan cukup.Terinspirasi oleh jawaban Enigmativity - anggaplah Anda memiliki dua (atau lebih) kelas, seperti
dan Anda ingin memanggil metode
Foo<T>
denganBar
danSquare
, yang dinyatakan sebagaiKemudian Anda dapat menerapkan metode Ekstensi seperti:
Dengan ini, Anda bisa memohon
Foo
seperti:yang bekerja untuk setiap kelas. Dalam hal ini, akan ditampilkan:
sumber