Apakah tujuan komponenisasi yang jelas dikalahkan dengan berbagi terlalu banyak informasi tipe antar perpustakaan? Mungkin Anda memerlukan penyimpanan data yang diketik dengan kuat dan efisien, tetapi akan sangat mahal jika Anda perlu memperbarui skema database Anda setiap kali model objek berevolusi, jadi apakah Anda mau?
lebih baik menyimpulkan skema tipenya saat runtime? Apakah Anda perlu mengirimkan komponen yang menerima objek pengguna sewenang-wenang danmenanganinya
dengan cara yang cerdas? Apakah Anda ingin kompiler perpustakaan dapat memberi tahu Anda secara terprogram apa tipenya?
untuk mempertahankan struktur data yang diketik dengan kuat sekaligus memaksimalkan fleksibilitas runtime, Anda mungkin ingin mempertimbangkan refleksi dan bagaimana hal ini dapat meningkatkan perangkat lunak Anda. Di kolom ini, saya akan menjelajahi namespace System.Reflection di Microsoft .NET Framework dan bagaimana hal ini dapat bermanfaat bagi pengalaman pengembangan Anda. Saya akan mulai dengan beberapa contoh sederhana dan diakhiri dengan cara menangani situasi serialisasi dunia nyata. Sepanjang jalan, saya akan menunjukkan bagaimana refleksi dan CodeDom bekerja sama untuk menangani data runtime secara efisien.
Sebelum saya mendalami System.Reflection, saya ingin membahas pemrograman reflektif secara umum. Pertama, refleksi dapat didefinisikan sebagai fungsionalitas apa pun yang disediakan oleh sistem pemrograman yang memungkinkan pemrogram memeriksa dan memanipulasi entitas kode tanpa pengetahuan sebelumnya tentang identitas atau struktur formalnya. Ada banyak hal yang perlu dibahas di bagian ini, jadi saya akan membahasnya satu per satu.
Pertama, apa yang disediakan oleh refleksi? Apa yang dapat Anda lakukan dengannya? Saya cenderung membagi tugas-tugas yang berpusat pada refleksi menjadi dua kategori: inspeksi dan manipulasi. Inspeksi memerlukan analisis objek dan tipe untuk mengumpulkan informasi terstruktur tentang definisi dan perilakunya. Terlepas dari beberapa ketentuan dasar, hal ini sering dilakukan tanpa sepengetahuan mereka sebelumnya. (Misalnya, di .NET Framework, semuanya diwarisi dari System.Object, dan referensi ke tipe objek sering kali menjadi titik awal umum untuk refleksi.)
Operasi secara dinamis memanggil kode menggunakan informasi yang dikumpulkan melalui inspeksi, membuat instance baru, atau bahkan jenis dan objek dapat dengan mudah direstrukturisasi secara dinamis. Satu hal penting yang perlu disampaikan adalah bahwa bagi sebagian besar sistem, memanipulasi tipe dan objek saat runtime akan mengakibatkan penurunan kinerja dibandingkan dengan melakukan operasi serupa secara statis dalam kode sumber. Ini merupakan trade-off yang diperlukan karena sifat refleksi yang dinamis, namun ada banyak tips dan praktik terbaik untuk mengoptimalkan kinerja refleksi (lihat msdn.microsoft.com/msdnmag/issues/05 untuk informasi lebih mendalam tentang pengoptimalan penggunaan refleksi /07/Refleksi).
Jadi, apa tujuan dari refleksi? Apa yang sebenarnya diperiksa dan dimanipulasi oleh pemrogram? Dalam definisi saya tentang refleksi, saya menggunakan istilah baru "entitas kode" untuk menekankan fakta bahwa dari sudut pandang pemrogram, teknik Refleksi terkadang mengaburkan batas di antara keduanya. benda dan jenis tradisional. Misalnya, tugas khas yang berpusat pada refleksi mungkin:
mulai dengan pegangan ke objek O dan gunakan refleksi untuk mendapatkan pegangan ke definisi terkait (tipe T).
Periksa tipe T dan dapatkan pegangan untuk metode M-nya.
Panggil metode M dari objek lain O' (juga bertipe T).
Perhatikan bahwa saya berpindah dari satu instance ke tipe dasarnya, dari tipe itu ke suatu metode, dan kemudian menggunakan pegangan metode untuk memanggilnya pada instance lain - jelas ini menggunakan pemrograman C# tradisional dalam kode sumber Teknologi tidak dapat mencapainya. Setelah membahas System.Reflection .NET Framework di bawah ini, saya akan menjelaskan situasi ini lagi dengan contoh nyata.
Beberapa bahasa pemrograman memberikan refleksi secara asli melalui sintaksis, sementara platform dan kerangka kerja lain (seperti .NET Framework) menyediakannya sebagai perpustakaan sistem. Terlepas dari bagaimana refleksi diberikan, kemungkinan penggunaan teknologi refleksi dalam situasi tertentu cukup kompleks. Kemampuan sistem pemrograman untuk memberikan refleksi bergantung pada banyak faktor: Apakah pemrogram memanfaatkan fitur-fitur bahasa pemrograman untuk mengekspresikan konsepnya dengan baik? Apakah kompiler menyematkan informasi terstruktur (metadata) yang cukup dalam keluaran untuk memfasilitasi analisis di masa depan? Interpretasi? Apakah ada subsistem runtime atau penerjemah host yang mencerna metadata ini? Apakah perpustakaan platform menyajikan hasil interpretasi ini dengan cara yang berguna bagi pemrogram?
Jika yang Anda maksud adalah sistem tipe berorientasi objek yang kompleks, tetapi itu muncul sebagai fungsi gaya C yang sederhana dalam kode, dan tidak ada struktur data formal, maka jelas tidak mungkin program Anda menyimpulkan secara dinamis bahwa penunjuk variabel tertentu v1 menunjuk ke instance objek bertipe T tertentu . Karena bagaimanapun juga, tipe T adalah sebuah konsep di kepala Anda, ia tidak pernah muncul secara eksplisit dalam pernyataan pemrograman Anda. Tetapi jika Anda menggunakan bahasa berorientasi objek yang lebih fleksibel (seperti C#) untuk mengekspresikan struktur abstrak program, dan secara langsung memperkenalkan konsep tipe T, maka kompiler akan mengubah ide Anda menjadi sesuatu yang nantinya dapat diteruskan melalui Logika yang sesuai untuk memahami bentuk, seperti yang disediakan oleh runtime bahasa umum (CLR) atau penerjemah bahasa dinamis.
Apakah refleksi sepenuhnya merupakan teknologi runtime yang dinamis? Ada kalanya sepanjang siklus pengembangan dan eksekusi ketika refleksi tersedia dan berguna bagi pengembang. Beberapa bahasa pemrograman diimplementasikan melalui kompiler yang berdiri sendiri yang mengubah kode tingkat tinggi secara langsung menjadi instruksi yang dapat dipahami oleh mesin. File output hanya menyertakan input yang dikompilasi, dan runtime tidak memiliki logika pendukung untuk menerima objek buram dan menganalisis definisinya secara dinamis. Hal ini persis terjadi pada banyak kompiler C tradisional. Karena hanya ada sedikit logika pendukung dalam target yang dapat dieksekusi, Anda tidak dapat melakukan banyak refleksi dinamis, tetapi kompiler menyediakan refleksi statis dari waktu ke waktu—misalnya, operator typeof yang ada di mana-mana memungkinkan pemrogram memeriksa pengidentifikasi tipe pada waktu kompilasi.
Situasi yang sama sekali berbeda adalah bahwa bahasa pemrograman yang ditafsirkan selalu dieksekusi melalui proses utama (bahasa scripting biasanya termasuk dalam kategori ini). Karena definisi lengkap dari program tersedia (sebagai kode sumber masukan), dikombinasikan dengan implementasi bahasa yang lengkap (sebagai penerjemah itu sendiri), semua teknik yang diperlukan untuk mendukung analisis diri telah tersedia. Bahasa dinamis ini sering kali memberikan kemampuan refleksi yang komprehensif, serta seperangkat alat yang kaya untuk analisis dinamis dan manipulasi program.
.NET Framework CLR dan bahasa hostnya seperti C# berada di tengah. Kompiler digunakan untuk mengubah kode sumber menjadi IL dan metadata. Yang terakhir ini tingkatnya lebih rendah atau kurang "logis" dibandingkan kode sumber, tetapi masih mempertahankan banyak struktur abstrak dan informasi tipe. Setelah CLR memulai dan menghosting program ini, perpustakaan System.Reflection dari perpustakaan kelas dasar (BCL) dapat menggunakan informasi ini dan mengembalikan informasi tentang tipe objek, tipe anggota, tanda tangan anggota, dan sebagainya. Selain itu, juga dapat mendukung panggilan, termasuk panggilan yang terlambat mengikat.
Refleksi di .NET
Untuk memanfaatkan refleksi saat pemrograman dengan .NET Framework, Anda dapat menggunakan namespace System.Reflection. Namespace ini menyediakan kelas yang merangkum banyak konsep runtime, seperti rakitan, modul, tipe, metode, konstruktor, bidang, dan properti. Tabel pada Gambar 1 menunjukkan bagaimana kelas-kelas di System.Reflection dipetakan ke rekan runtime konseptualnya.
Meskipun penting, System.Reflection.Assembly dan System.Reflection.Module terutama digunakan untuk mencari dan memuat kode baru ke dalam runtime. Pada kolom ini, saya tidak akan membahas bagian-bagian tersebut dan berasumsi bahwa semua kode yang relevan telah dimuat.
Untuk memeriksa dan memanipulasi kode yang dimuat, pola umumnya adalah System.Type. Biasanya, Anda memulai dengan mendapatkan instance System.Type dari kelas runtime yang diinginkan (melalui Object.GetType). Anda kemudian dapat menggunakan berbagai metode System.Type untuk mengeksplorasi definisi tipe di System.Reflection dan mendapatkan instance kelas lain. Misalnya, jika Anda tertarik pada metode tertentu dan ingin mendapatkan instance System.Reflection.MethodInfo dari metode ini (mungkin melalui Type.GetMethod). Demikian pula, jika Anda tertarik pada suatu bidang dan ingin mendapatkan instance System.Reflection.FieldInfo dari bidang ini (mungkin melalui Type.GetField).
Setelah Anda memiliki semua objek contoh refleksi yang diperlukan, Anda dapat melanjutkan dengan mengikuti langkah-langkah inspeksi atau manipulasi sesuai kebutuhan. Saat memeriksa, Anda menggunakan berbagai properti deskriptif di kelas reflektif untuk mendapatkan informasi yang Anda perlukan (Apakah ini tipe generik? Apakah ini metode instan?). Saat beroperasi, Anda dapat memanggil dan mengeksekusi metode secara dinamis, membuat objek baru dengan memanggil konstruktor, dan sebagainya.
Memeriksa Jenis dan Anggota
Mari kita beralih ke beberapa kode dan menjelajahi cara memeriksa menggunakan refleksi dasar. Saya akan fokus pada analisis tipe. Dimulai dengan sebuah objek, saya akan mengambil tipenya dan kemudian memeriksa beberapa anggota yang menarik (lihat Gambar 2).
Hal pertama yang perlu diperhatikan adalah bahwa dalam definisi kelas, sekilas tampak ada lebih banyak ruang untuk menjelaskan metode daripada yang saya harapkan. Dari manakah metode tambahan ini berasal? Siapa pun yang berpengalaman dalam hierarki objek .NET Framework akan mengenali metode ini yang diwarisi dari kelas dasar umum Object itu sendiri. (Sebenarnya, saya pertama kali menggunakan Object.GetType untuk mengambil tipenya.) Selain itu, Anda dapat melihat fungsi pengambil untuk properti tersebut. Sekarang, bagaimana jika Anda hanya memerlukan fungsi MyClass yang ditentukan secara eksplisit? Dengan kata lain, bagaimana Anda menyembunyikan fungsi yang diwarisi? Atau mungkin Anda hanya memerlukan fungsi instance yang ditentukan secara eksplisit
? Saya menemukan bahwa setiap orang bersedia menggunakan metode GetMethods kedua yang kelebihan beban, yang menerima parameter BindingFlags. Dengan menggabungkan nilai yang berbeda dari enumerasi BindingFlags, Anda dapat membuat fungsi hanya mengembalikan subset metode yang diinginkan. Ganti panggilan GetMethods dengan:
GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly |BindingFlags.Public)
Hasilnya, Anda mendapatkan keluaran berikut (perhatikan bahwa tidak ada fungsi pembantu statis dan fungsi yang diwarisi dari System.Object).
Contoh Demo Refleksi 1
Nama Tipe:
Nama Metode MyClass: MyMethod1
Nama Metode: MyMethod2
Nama Metode: get_MyProperty
Nama Properti: MyProperty
Bagaimana jika Anda mengetahui nama tipe (yang sepenuhnya memenuhi syarat) dan anggotanya sebelumnya? Bagaimana Anda melakukan pengambilan dari tipe enum ke Tipe konversi? Dengan kode pada dua contoh pertama, Anda sudah memiliki komponen dasar untuk mengimplementasikan browser kelas primitif. Anda dapat menemukan entitas runtime berdasarkan nama dan kemudian menghitung berbagai properti terkaitnya.
Kode pemanggilan dinamis
Sejauh ini saya telah memperoleh pegangan pada objek runtime (seperti tipe dan metode) untuk tujuan deskriptif saja, seperti mencetak namanya. Namun bagaimana Anda melakukan lebih banyak? Bagaimana sebenarnya Anda memanggil suatu metode?
Beberapa poin penting dalam contoh ini adalah: pertama, instance System.Type diambil dari instance MyClass, mc1, dan kemudian, instance MethodInfo diambil dari tipe itu. Terakhir, ketika MethodInfo dipanggil, ia terikat ke instance MyClass (mc2) lain dengan meneruskannya sebagai parameter pertama panggilan.
Seperti disebutkan sebelumnya, contoh ini mengaburkan perbedaan antara tipe dan penggunaan objek yang Anda harapkan terlihat dalam kode sumber. Logikanya, Anda mengambil pegangan ke suatu metode dan kemudian memanggil metode tersebut seolah-olah itu milik objek yang berbeda. Bagi pemrogram yang akrab dengan bahasa pemrograman fungsional, hal ini mungkin mudah; tetapi bagi pemrogram yang hanya akrab dengan C#, mungkin tidak terlalu intuitif untuk memisahkan implementasi objek dan pembuatan instance objek.
Menyatukan semuanya
Sejauh ini saya telah membahas prinsip dasar pengecekan dan pemanggilan, dan sekarang saya akan menggabungkannya dengan contoh nyata. Bayangkan Anda ingin menghadirkan perpustakaan dengan fungsi pembantu statis yang harus menangani objek. Namun pada saat desain, Anda tidak tahu jenis objek ini! Itu bergantung pada instruksi pemanggil fungsi tentang bagaimana dia ingin mengekstrak informasi yang berarti dari objek ini. Fungsi tersebut akan menerima kumpulan objek, dan deskriptor string dari metode tersebut. Ini kemudian akan mengulangi koleksi, memanggil metode setiap objek dan menggabungkan nilai yang dikembalikan dengan beberapa fungsi.
Untuk contoh ini, saya akan menyatakan beberapa batasan. Pertama, metode yang dijelaskan oleh parameter string (yang harus diimplementasikan oleh tipe dasar setiap objek) tidak akan menerima parameter apa pun dan akan mengembalikan bilangan bulat. Kode akan melakukan iterasi melalui kumpulan objek, memanggil metode yang ditentukan, dan secara bertahap menghitung rata-rata semua nilai. Terakhir, karena ini bukan kode produksi, saya tidak perlu khawatir tentang validasi parameter atau integer overflow saat menjumlahkan.
Saat menjelajahi kode contoh, Anda dapat melihat bahwa kesepakatan antara fungsi utama dan pembantu statis ComputeAverage tidak bergantung pada informasi jenis apa pun selain kelas dasar umum dari objek itu sendiri. Dengan kata lain, Anda dapat sepenuhnya mengubah jenis dan struktur objek yang ditransfer, tetapi selama Anda selalu dapat menggunakan string untuk mendeskripsikan metode yang mengembalikan bilangan bulat, ComputeAverage akan berfungsi dengan baik!
Masalah utama yang perlu diperhatikan adalah hal itu tersembunyi di Contoh terakhir terkait dengan MethodInfo (refleksi umum). Perhatikan bahwa dalam loop foreach ComputeAverage, kode hanya mengambil MethodInfo dari objek pertama dalam koleksi dan kemudian mengikatnya ke panggilan untuk semua objek berikutnya. Seperti yang ditunjukkan oleh pengkodean, ini berfungsi dengan baik - ini adalah contoh sederhana dari cache MethodInfo. Namun ada batasan mendasar di sini. Sebuah instance MethodInfo hanya dapat dipanggil oleh sebuah instance dengan tipe hierarki yang sama dengan objek yang diambilnya. Hal ini dimungkinkan karena instance IntReturner dan SonOfIntReturner (diwarisi dari IntReturner) diteruskan.
Dalam kode contoh, sebuah kelas bernama EnemyOfIntReturner telah disertakan, yang mengimplementasikan protokol dasar yang sama dengan dua kelas lainnya, namun tidak berbagi tipe umum apa pun. Dengan kata lain, antarmuka secara logis setara, tetapi tidak ada tumpang tindih pada tingkat tipe. Untuk mengeksplorasi penggunaan MethodInfo dalam situasi ini, coba tambahkan objek lain ke koleksi, dapatkan instance melalui "new EnemyOfIntReturner(10)", dan jalankan kembali contoh tersebut. Anda akan menemukan pengecualian yang menunjukkan bahwa MethodInfo tidak dapat digunakan untuk memanggil objek yang ditentukan karena sama sekali tidak ada hubungannya dengan tipe asli dari mana MethodInfo diperoleh (meskipun nama metode dan protokol yang mendasarinya setara). Agar kode Anda siap produksi, Anda harus bersiap menghadapi situasi ini.
Solusi yang mungkin adalah dengan menganalisis sendiri tipe semua objek yang masuk, dengan mempertahankan interpretasi hierarki tipe bersama (jika ada). Jika tipe objek berikutnya berbeda dari hierarki tipe yang diketahui, MethodInfo baru perlu diperoleh dan disimpan. Solusi lain adalah dengan menangkap TargetException dan mendapatkan kembali instance MethodInfo. Kedua solusi yang disebutkan di sini memiliki kelebihan dan kekurangannya masing-masing. Joel Pobar menulis artikel yang sangat bagus untuk majalah edisi Mei 2007 ini tentang buffering MethodInfo dan kinerja refleksi, yang sangat saya rekomendasikan.
Mudah-mudahan, contoh ini menunjukkan penambahan refleksi pada aplikasi atau kerangka kerja untuk menambah lebih banyak fleksibilitas untuk penyesuaian atau ekstensibilitas di masa depan. Memang benar, menggunakan refleksi bisa jadi sedikit rumit dibandingkan dengan logika setara dalam bahasa pemrograman asli. Jika Anda merasa bahwa menambahkan pengikatan akhir berbasis refleksi ke kode Anda terlalu rumit bagi Anda atau klien Anda (bagaimanapun juga, mereka memerlukan jenis dan kode mereka untuk diperhitungkan dalam kerangka kerja Anda), maka hal itu mungkin hanya diperlukan dalam fleksibilitas moderasi untuk mencapai keseimbangan tertentu.
Penanganan Tipe yang Efisien untuk Serialisasi
Sekarang kita telah membahas prinsip dasar refleksi .NET melalui beberapa contoh, mari kita lihat situasi dunia nyata. Jika perangkat lunak Anda berinteraksi dengan sistem lain melalui layanan Web atau teknologi jarak jauh di luar proses lainnya, Anda mungkin mengalami masalah serialisasi. Serialisasi pada dasarnya mengubah objek aktif yang menempati memori menjadi format data yang cocok untuk transmisi online atau penyimpanan disk.
Namespace System.Xml.Serialization di .NET Framework menyediakan mesin serialisasi yang kuat dengan XmlSerializer, yang dapat mengambil objek terkelola apa pun dan mengonversinya menjadi XML (data XML juga dapat dikonversi kembali ke instance objek yang diketik di masa mendatang, Proses ini disebut deserialisasi). Kelas XmlSerializer adalah perangkat lunak yang kuat dan siap untuk perusahaan yang akan menjadi pilihan pertama Anda jika Anda menghadapi masalah serialisasi dalam proyek Anda. Namun untuk tujuan pendidikan, mari kita jelajahi cara mengimplementasikan serialisasi (atau contoh penanganan tipe runtime serupa lainnya).
Pertimbangkan ini: Anda memberikan kerangka kerja yang mengambil contoh objek dari tipe pengguna yang berubah-ubah dan mengubahnya menjadi beberapa format data cerdas. Misalnya, asumsikan Anda memiliki objek yang tinggal di memori bertipe Address seperti yang ditunjukkan di bawah ini:
(pseudocode)
alamat kelas
{
ID AlamatID;
Jalan String, Kota;
Negara Bagian Tipe Negara;
Kode PosJenis Kode Pos;
}
Bagaimana cara menghasilkan representasi data yang sesuai untuk digunakan nanti? Mungkin rendering teks sederhana akan menyelesaikan masalah ini:
Alamat: 123
Street: 1 Microsoft Way
Kota: Redmond
Negara Bagian: WA
Zip: 98052
Jika data formal yang perlu dikonversi telah dipahami sepenuhnya diketik terlebih dahulu (misalnya saat menulis kode sendiri), segalanya menjadi sangat sederhana:
foreach(Alamat a di Daftar Alamat)
{
Console.WriteLine(“Alamat:{0}”, a.ID);
Console.WriteLine(“tJalan:{0}”, a.Jalan);
... // dan seterusnya
}
Namun, segalanya bisa menjadi sangat menarik jika Anda tidak mengetahui sebelumnya tipe data apa yang akan Anda temui saat runtime. Bagaimana Anda menulis kode kerangka umum seperti ini?
MyFramework.TranslateObject (input objek, output MyOutputWriter)
Pertama, Anda perlu memutuskan tipe anggota mana yang berguna untuk serialisasi. Kemungkinannya termasuk menangkap hanya anggota dari tipe tertentu, seperti tipe sistem primitif, atau menyediakan mekanisme bagi pembuat tipe untuk menunjukkan anggota mana yang perlu diserialkan, seperti menggunakan properti khusus sebagai penanda pada anggota tipe). Anda hanya dapat menangkap anggota dari tipe tertentu, seperti tipe sistem primitif, atau pembuat tipe dapat menyatakan anggota mana yang perlu diserialkan (mungkin dengan menggunakan properti khusus sebagai penanda pada anggota tipe).
Setelah Anda mendokumentasikan anggota struktur data yang perlu dikonversi, yang perlu Anda lakukan adalah menulis logika untuk menghitung dan mengambilnya dari objek masuk. Refleksi melakukan pekerjaan berat di sini, memungkinkan Anda menanyakan struktur data dan nilai data.
Demi kesederhanaan, mari kita rancang mesin konversi ringan yang mengambil sebuah objek, mendapatkan semua nilai properti publiknya, mengonversinya menjadi string dengan memanggil ToString secara langsung, lalu membuat serial nilainya. Untuk objek tertentu bernama "input", algoritmenya kira-kira sebagai berikut:
panggil input.GetType untuk mengambil instance System.Type, yang menjelaskan struktur dasar input.
Gunakan Type.GetProperties dan parameter BindingFlags yang sesuai untuk mengambil properti publik sebagai instance PropertyInfo.
Properti diambil sebagai pasangan nilai kunci menggunakan PropertyInfo.Name dan PropertyInfo.GetValue.
Panggil Object.ToString pada setiap nilai untuk mengonversinya (secara dasar) ke format string.
Kemas nama tipe objek dan kumpulan nama properti dan nilai string ke dalam format serialisasi yang benar.
Algoritme ini menyederhanakan berbagai hal secara signifikan sekaligus menangkap maksud dari pengambilan struktur data runtime dan mengubahnya menjadi data yang dapat mendeskripsikan dirinya sendiri. Namun ada masalah: kinerja. Seperti disebutkan sebelumnya, refleksi sangat mahal untuk pemrosesan tipe dan pengambilan nilai. Dalam contoh ini, saya melakukan analisis tipe lengkap pada setiap instance dari tipe yang disediakan.
Bagaimana jika ada kemungkinan untuk menangkap atau mempertahankan pemahaman Anda tentang struktur suatu tipe sehingga Anda dapat dengan mudah mengambilnya nanti dan secara efisien menangani instance baru dari tipe tersebut; dengan kata lain, lanjutkan ke langkah #3 dalam contoh algoritma? Beritanya adalah hal ini dapat dilakukan dengan menggunakan fitur-fitur di .NET Framework. Setelah Anda memahami struktur data suatu tipe, Anda dapat menggunakan CodeDom untuk secara dinamis menghasilkan kode yang mengikat struktur data tersebut. Anda dapat membuat rakitan pembantu yang berisi kelas pembantu dan metode yang mereferensikan tipe masuk dan mengakses propertinya secara langsung (seperti properti lainnya dalam kode terkelola), sehingga pemeriksaan tipe hanya memengaruhi kinerja satu kali.
Sekarang saya akan memperbaiki algoritma ini. Tipe baru:
Dapatkan instance System.Type yang sesuai dengan tipe ini.
Gunakan berbagai pengakses System.Type untuk mengambil skema (atau setidaknya subset skema yang berguna untuk serialisasi), seperti nama properti, nama bidang, dll.
Gunakan informasi skema untuk menghasilkan rakitan pembantu (melalui CodeDom) yang terhubung dengan tipe baru dan menangani instance secara efisien.
Gunakan kode dalam rakitan pembantu untuk mengekstrak data instans.
Buat serial data sesuai kebutuhan.
Untuk semua data masuk dari jenis tertentu, Anda dapat melompat ke langkah #4 dan mendapatkan peningkatan kinerja yang besar dengan memeriksa setiap instance secara eksplisit.
Saya mengembangkan perpustakaan serialisasi dasar yang disebut SimpleSerialization yang mengimplementasikan algoritma ini menggunakan refleksi dan CodeDom (dapat diunduh di kolom ini). Komponen utamanya adalah kelas bernama SimpleSerializer, yang dibangun oleh pengguna dengan instance System.Type. Di konstruktor, instance SimpleSerializer baru menganalisis tipe tertentu dan menghasilkan perakitan sementara menggunakan kelas pembantu. Kelas pembantu terikat erat dengan tipe data tertentu dan menangani instance seolah-olah Anda sedang menulis kode dengan pengetahuan lengkap sebelumnya tentang tipe tersebut.
Kelas SimpleSerializer memiliki tata letak berikut:
kelas SimpleSerializer
{
SimpleSerializer kelas publik (Tipe dataType);
public void Serialize (input objek, penulis SimpleDataWriter);
}
Sungguh menakjubkan! Konstruktor melakukan pekerjaan berat: ia menggunakan refleksi untuk menganalisis struktur tipe dan kemudian menggunakan CodeDom untuk menghasilkan rakitan pembantu. Kelas SimpleDataWriter hanyalah sink data yang digunakan untuk mengilustrasikan pola serialisasi umum.
untuk
menyelesaikan tugas:
SimpleSerializer mySerializer = new SimpleSerializer(typeof(Address));
SimpleDataWriter writer = new SimpleDataWriter(
)
;
disarankan agar Anda mencoba sendiri kode contohnya, terutama pustaka SimpleSerialization. Saya telah menambahkan komentar ke beberapa bagian menarik dari SimpleSerializer, semoga membantu. Tentu saja, jika Anda memerlukan serialisasi yang ketat dalam kode produksi, Anda benar-benar harus mengandalkan teknologi yang disediakan di .NET Framework (seperti XmlSerializer). Namun jika Anda merasa perlu bekerja dengan tipe arbitrer saat runtime dan menanganinya secara efisien, saya harap Anda akan menggunakan perpustakaan SimpleSerialization saya sebagai solusi Anda.