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:
class Alpha { }
class Beta { }
class Service
{
public void Process<T>(T item)
{
Console.WriteLine("item.GetType(): " + item.GetType()
+ "\ttypeof(T): " + typeof(T));
}
}
class Program
{
static void Main(string[] args)
{
var a = new Alpha();
var b = new Beta();
var service = new Service();
service.Process(a); // Same as "service.Process<Alpha>(a)"
service.Process(b); // Same as "service.Process<Beta>(b)"
var objects = new object[] { a, b };
foreach (var o in objects)
{
service.Process(o); // Same as "service.Process<object>(o)"
}
foreach (var o in objects)
{
dynamic dynObj = o;
service.Process(dynObj); // Or write "service.Process((dynamic)o)"
}
}
}
Dan inilah output dari program ini:
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
item.GetType(): Alpha typeof(T): System.Object
item.GetType(): Beta typeof(T): System.Object
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
Process
adalah metode instance generik yang menulis tipe sebenarnya dari argumen yang diteruskan (dengan menggunakan GetType()
metode) dan tipe parameter generik (dengan menggunakan typeof
operator).
Dengan melemparkan argumen objek untuk dynamic
mengetik, kami menunda menyediakan parameter tipe hingga runtime. Ketika Process
metode dipanggil dengan dynamic
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:
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
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:
class Program
{
static void Main(string[] args)
{
object obj = new Alpha();
Helper((dynamic)obj);
}
public static void Helper<T>(T obj)
{
GenericMethod<T>();
}
public static void GenericMethod<T>()
{
Console.WriteLine("GenericMethod<" + typeof(T) + ">");
}
}
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 string Type.GetMethod
dan argumen sebagai array objek MethodInfo.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.
interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }
class Program
{
static void Main(string[] args)
{
var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
for (int i = 0; i < objects.Length; i++)
{
ProcessItem((dynamic)objects[i], "test" + i, i);
//ProcesItm((dynamic)objects[i], "test" + i, i);
//compiler error: The name 'ProcesItm' does not
//exist in the current context
//ProcessItem((dynamic)objects[i], "test" + i);
//error: No overload for method 'ProcessItem' takes 2 arguments
}
}
static string ProcessItem<T>(T item, string text, int number)
where T : IItem
{
Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
typeof(T), text, number);
return "OK";
}
static void ProcessItem(BarItem item, string text, int number)
{
Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
}
}
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 memanggil ProcessItem
metode dengan objek BarItem
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 argumen Alpha
tipe karena tidak ada metode yang dapat menangani objek ini (metode generik memiliki kendala where T : IItem
dan Alpha
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:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
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:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
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.