DLR Grokay

Kata Pengantar Penerjemah

Ini lebih menceritakan kembali gratis, bukan terjemahan. Saya memasukkan dalam artikel ini hanya bagian-bagian asli yang secara langsung terkait dengan mekanisme internal DLR atau menjelaskan ide-ide penting. Catatan akan dilampirkan dalam tanda kurung siku.

Banyak pengembang .NET telah mendengar tentang Dynamic Language Runtime (DLR), tetapi hampir tidak tahu apa-apa tentang itu. Pengembang yang menulis dalam bahasa seperti C # atau Visual Basic menghindari bahasa pengetikan dinamis karena takut akan masalah skalabilitas yang terkait secara historis. Mereka juga khawatir tentang fakta bahwa bahasa seperti Python atau Ruby tidak melakukan pengecekan tipe pada waktu kompilasi, yang dapat menyebabkan kesalahan runtime yang sulit ditemukan dan diperbaiki. Ini adalah ketakutan yang beralasan yang dapat menjelaskan mengapa DLR tidak populer di kalangan mayoritas pengembang NET. Bahkan dua tahun setelah rilis resmi [artikel ini sudah cukup tua, tetapi tidak ada yang berubah sejak itu] . Bagaimanapun, .NET Runtime yang berisi kata Dynamic dan Language dalam namanya harus dirancang secara ketat untuk mendukung bahasa seperti Python, bukan?

Perlambat. Sementara DLR benar-benar dirancang untuk mendukung implementasi Iron dari Python dan Ruby di .NET Framework, arsitekturnya menyediakan abstraksi yang jauh lebih dalam.



Di bawah tenda, DLR menawarkan rangkaian antarmuka yang kaya untuk komunikasi antar-proses [Inter-Process Communication (IPC)]. Selama bertahun-tahun, pengembang telah melihat banyak alat Microsoft untuk interaksi antara aplikasi: DDE, DCOM, ActiveX, .Net Remoting, WCF, OData. Daftar ini bisa berlangsung lama. Ini adalah parade akronim yang hampir tak ada habisnya, yang masing-masing mewakili teknologi yang menjanjikan bahwa tahun ini akan lebih mudah untuk bertukar data atau memanggil kode jarak jauh daripada sebelumnya.

Bahasa bahasa


Pertama kali saya mendengar Jim Hugunin berbicara tentang DLR, pidatonya mengejutkan saya. Jim membuat implementasi Python untuk Java Virtual Machine (JVM) yang dikenal sebagai Jython. Sesaat sebelum pertunjukan, ia bergabung dengan Microsoft untuk membuat IronPython untuk .NET. Berdasarkan latar belakangnya, saya berharap dia fokus pada bahasa, tetapi sebaliknya, Jim berbicara hampir sepanjang waktu tentang hal-hal yang muskil seperti pohon ekspresi, pengiriman panggilan dinamis, dan mekanisme panggilan caching. Jim menggambarkan satu set layanan kompilasi runtime yang memungkinkan dua bahasa untuk berinteraksi satu sama lain dengan hampir tidak ada kerugian dalam kinerja.

Selama pidato ini, saya menuliskan istilah yang muncul di kepala saya ketika saya mendengar Jim menceritakan kembali arsitektur DLR: bahasa bahasa. Empat tahun kemudian, nama panggilan ini masih menjadi ciri DLR dengan sangat akurat. Namun, setelah mendapatkan pengalaman penggunaan di dunia nyata, saya menyadari bahwa DLR bukan hanya tentang kompatibilitas bahasa. Berkat dukungan tipe dinamis dalam C # dan Visual Basic, DLR dapat bertindak sebagai gateway dari bahasa .NET favorit kami ke data dan kode di sistem jarak jauh apa pun, apa pun jenis peralatan atau perangkat lunak yang digunakan terakhir.



Untuk memahami ide di balik DLR, yang merupakan mekanisme terintegrasi dalam bahasa IPC, mari kita mulai dengan contoh yang tidak ada hubungannya dengan pemrograman dinamis. Bayangkan dua sistem komputer: satu disebut inisiator, dan yang kedua - sistem target. Inisiator perlu menjalankan fungsi foo pada sistem target, melewati serangkaian parameter tertentu, dan mendapatkan hasilnya. Setelah sistem target ditemukan, pemrakarsa harus memberikan semua informasi yang diperlukan untuk pelaksanaan fungsi dalam format yang dapat dimengerti olehnya. Minimal, informasi ini akan mencakup nama fungsi dan parameter yang diteruskan. Setelah membongkar permintaan dan memvalidasi parameter, sistem target akan menjalankan fungsi foo. Setelah itu, ia harus mengemas hasilnya, termasuk kesalahan yang terjadi selama eksekusi, dan mengirimkannya kembali ke inisiator. Akhirnya, pemrakarsa harus dapat membongkar hasil dan memberitahukan tujuan. Pola respons permintaan ini cukup umum dan pada tingkat tinggi menggambarkan operasi hampir semua mekanisme IPC.

Objek dinamis


Untuk memahami bagaimana DLR mengimplementasikan pola yang disajikan, mari kita lihat salah satu kelas pusat DLR: DynamicMetaObject . Kita mulai dengan mengeksplorasi tiga dari dua belas metode utama dari jenis ini:

  1. BindCreateInstance - membuat atau mengaktifkan objek
  2. BindInvokeMember - panggil metode enkapsulasi
  3. BindInvoke - eksekusi objek (sebagai fungsi)

Saat Anda perlu menjalankan metode pada sistem jarak jauh, Anda harus terlebih dahulu membuat turunan dari tipe tersebut. Tentu saja, tidak semua sistem berorientasi objek, jadi istilah "instance" bisa menjadi metafora. Bahkan, layanan yang kita butuhkan dapat diimplementasikan sebagai kumpulan objek atau sebagai singleton, sehingga istilah "aktivasi" atau "koneksi" dapat digunakan dengan hak yang sama dengan "instance".

Kerangka kerja lain mengikuti pola yang sama. Sebagai contoh, COM menyediakan fungsi CoCreateInstance untuk membuat objek. Di .NET Remoting, Anda bisa menggunakan metode CreateInstance dari kelas System.Activator . DLR DynamicMetaObject menyediakan BindCreateInstance untuk tujuan yang sama.

Setelah menggunakan metode BindCreateInstance , sesuatu yang dibuat bisa menjadi tipe yang memperlihatkan beberapa metode. Metode metaobject BindInvokeMember digunakan untuk mengikat operasi yang dapat memanggil suatu fungsi. Pada gambar di atas, string foo dapat dikirimkan sebagai parameter untuk menunjukkan kepada pengikat bahwa metode dengan nama itu harus dipanggil. Selain itu termasuk informasi tentang jumlah argumen, nama mereka dan bendera khusus yang menunjukkan kepada pengikat apakah mungkin untuk mengabaikan kasus ketika mencari elemen bernama yang sesuai. Lagi pula, tidak semua bahasa peka terhadap huruf besar-kecil.

Ketika sesuatu yang dikembalikan dari BindCreateInstance hanya satu fungsi (atau mendelegasikan), metode BindInvoke digunakan. Untuk memperjelas gambar, mari kita lihat potongan kecil kode dinamis berikut:

delegate void IntWriter(int n); void Main() { dynamic Write = new IntWriter(Console.WriteLine); Write(5); } 

Kode ini bukan cara terbaik untuk mencetak angka 5 ke konsol. Pengembang yang baik tidak akan pernah menggunakan sesuatu yang sia-sia. Namun, kode ini menggambarkan penggunaan variabel dinamis yang nilainya merupakan delegasi yang dapat digunakan sebagai fungsi. Jika tipe delegasi mengimplementasikan antarmuka IDynamicMetaObjectProvider , maka metode BindInvoke dari DynamicMetaObject akan digunakan untuk mengikat operasi ke pekerjaan nyata. Ini karena kompiler mengakui bahwa objek Write dinamis secara sintaksis digunakan sebagai fungsi. Sekarang pertimbangkan potongan kode lain untuk memahami kapan kompiler akan menghasilkan BindInvokeMember :

 class Writer : IDynamicMetaObjectProvider { public void Write(int n) { Console.WriteLine(n); } //    } void Main() { dynamic Writer = new Writer(); Writer.Write(7); } 

Saya akan mengabaikan implementasi antarmuka dalam contoh kecil ini, karena akan membutuhkan banyak kode untuk menunjukkan ini dengan benar. Dalam contoh singkat ini, kami mengimplementasikan objek meta dinamis hanya dengan beberapa baris kode.

Satu hal penting untuk dipahami adalah bahwa kompiler mengakui bahwa Writer.Write (7) adalah operasi akses elemen. Apa yang biasa kita sebut "operator titik" di C # secara resmi disebut "operator akses anggota tipe". Kode DLR yang dihasilkan oleh kompiler dalam kasus ini pada akhirnya akan memanggil BindInvokeMember , di mana ia akan meneruskan string Write dan parameter nomor 7 ke operasi yang mampu melakukan panggilan. Singkatnya, BindInvoke digunakan untuk memanggil objek dinamis sebagai fungsi, sedangkan BindInvokeMember digunakan untuk memanggil metode sebagai elemen dari objek dinamis.

Akses properti melalui DynamicMetaObject


Dapat dilihat dari contoh di atas bahwa kompiler menggunakan sintaks bahasa untuk menentukan operasi pengikatan DLR mana yang harus dilakukan. Jika Anda menggunakan Visual Basic untuk bekerja dengan objek dinamis, maka semantiknya akan digunakan. Operator akses (titik), tentu saja, diperlukan tidak hanya untuk mengakses metode. Anda dapat menggunakannya untuk mengakses properti. Objek meta DLR menyediakan tiga metode untuk mengakses properti objek dinamis:

  1. BindGetMember - dapatkan nilai properti
  2. BindSetMember - tetapkan nilai properti
  3. BindDeleteMember - hapus item

Tujuan BindGetMember dan BindSetMember harus jelas. Apalagi sekarang Anda tahu bagaimana mereka berhubungan dengan bagaimana .NET bekerja dengan properti. Ketika kompiler menghitung properti get ("read") dari objek dinamis, ia menggunakan panggilan ke BindGetMember . Ketika compiler menghitung set ("record"), ia menggunakan BindSetMember .

Representasi objek sebagai array


Beberapa kelas adalah wadah untuk instance dari jenis lain. DLR tahu bagaimana menangani kasus-kasus seperti itu. Setiap metode objek-meta "berorientasi-array" memiliki postfix "Indeks":

  1. BindGetIndex - dapatkan nilai berdasarkan indeks
  2. BindSetIndex - set nilai berdasarkan indeks
  3. BindDeleteIndex - menghapus nilai berdasarkan indeks

Untuk memahami bagaimana BindGetIndex dan BindSetIndex digunakan , bayangkan kelas pembungkus JavaBridge yang dapat memuat file dengan kelas Java dan memungkinkan Anda untuk menggunakannya dari kode .NET tanpa kesulitan. Wrapper semacam itu dapat digunakan untuk memuat kelas Java Pelanggan , yang berisi beberapa kode ORM. Objek meta DLR dapat digunakan untuk memanggil kode ORM ini dari .NET dalam gaya C # klasik. Di bawah ini adalah contoh kode yang menunjukkan bagaimana JavaBridge dapat bekerja dalam praktik:

 JavaBridge java = new JavaBridge(); dynamic customers = java.Load("Customer.class"); dynamic Jason = customers["Bock"]; Jason.Balance = 17.34; customers["Wagner"] = new Customer("Bill"); 

Karena baris ketiga dan kelima menggunakan operator akses berdasarkan indeks ([]), kompiler mengenali ini dan menggunakan metode BindGetIndex dan BindSetIndex ketika bekerja dengan objek meta yang dikembalikan dari JavaBridge . Dapat dipahami bahwa implementasi metode ini pada objek yang dikembalikan akan meminta eksekusi metode dari JVM melalui Java Remote Method Invocation (RMI). Dalam skenario ini, DLR bertindak sebagai jembatan antara C # dan bahasa lain dengan pengetikan statis. Semoga ini menjelaskan mengapa saya menyebut DLR "bahasa bahasa".

Metode BindDeleteMember , sama seperti BindDeleteIndex , tidak dimaksudkan untuk digunakan dari bahasa dengan pengetikan statis seperti C # dan Visual Basic, karena mereka tidak mendukung konsep itu sendiri. Namun, Anda dapat setuju untuk mempertimbangkan "menghilangkan" beberapa operasi yang diungkapkan dengan menggunakan bahasa, jika itu berguna bagi Anda. Misalnya, Anda dapat mengimplementasikan BindDeleteMember sebagai membatalkan elemen menurut indeks.

Transformasi dan Operator


Kelompok terakhir metode metaobjek DLR adalah tentang penanganan operator dan transformasi.

  1. BindConvert - mengonversi objek ke tipe lain
  2. BindBinaryOperation - menggunakan operator biner pada dua operan
  3. BindUnaryOperation - menggunakan operator unary pada satu operan

Metode BindConvert digunakan ketika kompiler menyadari bahwa objek perlu dikonversi ke tipe lain yang dikenal. Konversi tersirat terjadi ketika hasil panggilan dinamis ditugaskan ke variabel dengan tipe statis. Sebagai contoh, dalam contoh C # berikut, menetapkan variabel y mengarah ke panggilan implisit ke BindConvert :

 dynamic x = 13; int y = x + 11; 

Metode BindBinaryOperation dan BindUnaryOperation selalu digunakan ketika operasi aritmatika ("+") atau peningkatan ("++") ditemui. Pada contoh di atas, menambahkan variabel dinamis x ke konstanta 11 akan memanggil metode BindBinaryOperation . Ingat contoh kecil ini, kami menggunakannya di bagian berikutnya untuk menggedor kelas DLR kunci lain yang disebut CallSite.

Pengiriman dinamis dengan CallSite


Jika pengantar Anda untuk DLR tidak lebih dari menggunakan kata kunci dinamis , maka Anda mungkin tidak akan pernah tahu tentang keberadaan CallSite di .NET Framework. Tipe sederhana ini, secara resmi dikenal sebagai CallSite < T > , berada di System.Runtime.CompilerServices namespace . Ini adalah "sumber daya" dari metaprogramming: diisi dengan segala macam metode optimasi yang membuat kode .NET dinamis cepat dan efisien. Saya akan menyebutkan aspek kinerja CallSite < T > di akhir artikel.

Sebagian besar yang dilakukan CallSite dalam kode .NET dinamis melibatkan pembuatan dan kompilasi kode pada saat runtime. Penting untuk dicatat bahwa kelas CallSite < T > terletak di namespace yang berisi kata-kata " Runtime " dan " CompilerServices ". Jika DLR adalah "bahasa bahasa", maka CallSite < T > adalah salah satu konstruksi tata bahasa yang paling penting. Mari kita lihat contoh kita dari bagian sebelumnya lagi untuk mengenal CallSite dan bagaimana kompiler menanamkannya dalam kode Anda.

 dynamic x = 13; int y = x + 11; 

Seperti yang sudah Anda ketahui, metode BindBinaryOperaion dan BindConvert akan dipanggil untuk menjalankan kode ini. Alih-alih menunjukkan daftar panjang kode MSIL dibongkar yang dihasilkan oleh kompiler, saya membuat diagram:



Ingat bahwa kompiler menggunakan sintaks bahasa untuk menentukan metode tipe dinamis mana yang harus dijalankan. Dalam contoh kami, dua operasi dilakukan: menambahkan variabel x ke nomor ( Site2 ) dan melemparkan hasilnya ke int ( Site1 ). Setiap tindakan ini berubah menjadi CallSite, yang disimpan dalam wadah khusus. Seperti yang Anda lihat dalam diagram, CallSites dibuat dalam urutan terbalik, tetapi dipanggil dengan cara yang benar.

Pada gambar Anda dapat melihat bahwa metode metaobject BindConvert dan BindBinaryOperation dipanggil segera sebelum operasi "buat CallSite1" dan "create CallSite2". Namun, operasi terikat hanya dilakukan pada bagian paling akhir. Saya berharap visualisasi membantu Anda memahami bahwa metode pengikatan dan memanggil mereka adalah operasi yang berbeda dalam konteks DLR. Selain itu, pengikatan hanya terjadi sekali, sementara panggilan terjadi sebanyak yang diperlukan, menggunakan kembali CallSites yang sudah diinisialisasi untuk mengoptimalkan kinerja.

Ikuti cara mudahnya


Di jantung DLR, pohon ekspresi digunakan untuk menghasilkan fungsi yang terikat pada dua belas metode pengikatan yang disajikan di atas. Banyak pengembang terus-menerus dihadapkan dengan pohon ekspresi menggunakan LINQ, tetapi hanya beberapa yang memiliki pengalaman yang cukup dalam untuk sepenuhnya mengimplementasikan kontrak IDynamicMetaObjectProvider . Untungnya, .NET Framework berisi kelas dasar yang disebut DynamicObject yang menangani sebagian besar pekerjaan.

Untuk membuat kelas dinamis Anda sendiri, yang harus Anda lakukan adalah mewarisi dari DynamicObject dan menerapkan dua belas metode berikut:

  1. TryCreateInstance
  2. TryInvokeMember
  3. Tryinvoke
  4. TryGetMember
  5. TrySetMember
  6. TryDeleteMember
  7. TryGetIndex
  8. TrySetIndex
  9. TryDeleteIndex
  10. Coba konversi
  11. TryBinaryOperation
  12. TryUnaryOperation

Apakah nama metode terlihat akrab? Anda harus, karena Anda baru saja selesai mempelajari elemen-elemen dari kelas DynamicMetaObject Abstrak, yang mencakup metode seperti BindCreateInstance dan BindInvoke . Kelas DynamicMetaObject menyediakan implementasi untuk IDynamicMetaObjectProvider , yang mengembalikan DynamicMetaObject dari satu-satunya metode. Operasi yang terkait dengan implementasi dasar objek meta cukup mendelegasikan panggilan mereka ke metode yang dimulai dengan "Coba" pada instance DynamicObject . Yang perlu Anda lakukan adalah membebani metode seperti TryGetMember dan TrySetMember di kelas yang diwarisi dari DynamicObject , sementara objek meta akan mengambil semua pekerjaan kotor dengan pohon ekspresi.

Caching


[Anda dapat membaca lebih lanjut tentang caching di artikel saya sebelumnya tentang DLR ]

Perhatian terbesar ketika bekerja dengan bahasa dinamis untuk pengembang adalah kinerja. DLR mengambil tindakan luar biasa untuk menghilangkan pengalaman ini. Secara singkat saya menyebutkan fakta bahwa CallSite < T > berada di namespace bernama System.Runtime.CompilerServices . Di namespace yang sama terletak beberapa kelas lain yang menyediakan caching bertingkat. Menggunakan tipe-tipe ini, DLR mengimplementasikan tiga level utama caching untuk mempercepat operasi dinamis:

  1. Cache global
  2. Cache lokal
  3. Tembolok Delegasi Polimorfik

Cache digunakan untuk menghindari pemborosan sumber daya yang tidak perlu untuk membuat binding untuk CallSite tertentu. Jika dua objek tipe string diteruskan ke metode dinamis yang mengembalikan int , maka cache global atau lokal akan menyimpan hasil pengikatan. Ini akan sangat menyederhanakan panggilan berikutnya.

Tembolok delegasi, yang terletak di dalam CallSite sendiri, disebut polimorfik, karena delegasi ini dapat mengambil bentuk berbeda tergantung pada kode dinamis mana yang dijalankan dan aturan mana dari cache lain yang digunakan untuk membuatnya. Cache delegate juga kadang-kadang disebut inline cache. Alasan untuk menggunakan istilah ini adalah bahwa ekspresi yang dihasilkan oleh DLR dan pengikatnya dikonversi ke kode MSIL yang melewati kompilasi JIT, seperti kode .NET lainnya. Kompilasi saat runtime terjadi bersamaan dengan eksekusi "normal" program Anda. Jelas bahwa mengubah kode dinamis on-the-fly menjadi kode MSIL yang dikompilasi selama eksekusi program dapat sangat mempengaruhi kinerja aplikasi, sehingga mekanisme caching sangat penting.

Source: https://habr.com/ru/post/id469075/


All Articles