مقدمة المترجم
هذا هو أكثر رواية حرة ، وليس الترجمة. لقد أدرجت في هذه المقالة فقط الأجزاء الأصلية التي ترتبط مباشرة بالآليات الداخلية لـ DLR أو تشرح الأفكار المهمة. سيتم إرفاق الملاحظات بين قوسين مربعين.سمعت العديد من مطوري .NET عن وقت تشغيل اللغة الديناميكي (DLR) ، لكنهم لا يعرفون شيئًا عن ذلك. يتجنب المطورون الذين يكتبون بلغات مثل C # أو Visual Basic لغات الكتابة الديناميكية خوفًا من مشكلات قابلية التوسع المرتبطة تاريخياً. إنهم قلقون أيضًا من حقيقة أن لغات مثل Python أو Ruby لا تؤدي عملية تدقيق الكتابة في وقت الترجمة ، مما قد يؤدي إلى أخطاء في وقت التشغيل يصعب العثور عليها وإصلاحها. هذه مخاوف راسخة قد تفسر سبب عدم شهرة DLR بين غالبية مطوري .NET حتى بعد عامين من الإصدار الرسمي
[المقال قديم جدًا ، لكن لم يتغير شيء منذ ذلك الحين] . بعد كل شيء ، يجب تصميم أي
وقت تشغيل .NET يحتوي على الكلمات
الديناميكية واللغة باسمها بدقة لدعم لغات مثل بيثون ، أليس كذلك؟
تبطئ. بينما تم تصميم DLR بالفعل لدعم تطبيق Iron لبيثون وروبي في. NET Framework ، فإن بنيته توفر تجريدات أعمق بكثير.

تحت الغطاء ، تقدم DLR مجموعة غنية من الواجهات للاتصال بين العمليات [الاتصال بين العمليات (IPC)]. على مر السنين ، رأى المطورون العديد من أدوات Microsoft للتفاعل بين التطبيقات: DDE و DCOM و ActiveX و .Net Remoting و WCF و OData. قد تستمر هذه القائمة لفترة طويلة. هذا عرض لا نهاية له تقريبًا من المختصرات ، يمثل كل منها تقنية تعد بأن هذا العام سيكون من الأسهل تبادل البيانات أو الاتصال بالرمز البعيد عن ذي قبل.
لغة اللغات
في المرة الأولى التي سمعت فيها جيم هوغنين يتحدث عن DLR ، فاجأني كلامه. قام Jim بإنشاء تطبيق Python لـ Java Virtual Machine (JVM) المعروف باسم Jython. قبل وقت قصير من العرض ، انضم إلى Microsoft لإنشاء IronPython لـ .NET. بناءً على خلفيته ، توقعت منه أن يركز على اللغة ، ولكن بدلاً من ذلك ، تحدث جيم طوال الوقت تقريبًا عن أشياء شريرة مثل أشجار التعبير وإرسال المكالمات الديناميكي واستدعاء آليات التخزين المؤقت. وصف جيم مجموعة من خدمات تجميع وقت التشغيل التي سمحت لأي لغتين بالتفاعل مع بعضها البعض دون أي خسارة في الأداء.
خلال هذا الخطاب ، كتبت تعبيرًا ظهر في رأسي عندما سمعت جيم وهو يتحدث عن بنية DLR: لغة اللغات. بعد أربع سنوات ، لا يزال هذا اللقب يميز DLR بدقة شديدة. ومع ذلك ، بعد اكتساب خبرة الاستخدام في العالم الحقيقي ، أدركت أن DLR لا يتعلق فقط بتوافق اللغات. بفضل دعم الأنواع الديناميكية في C # و Visual Basic ، يمكن DLR بمثابة بوابة من لغات .NET المفضلة لدينا إلى البيانات والرمز في أي نظام بعيد ، بغض النظر عن نوع المعدات أو البرامج التي يستخدمها الأخير.

لفهم الفكرة الكامنة وراء DLR ، وهي آلية متكاملة في لغة IPC ، لنبدأ بمثال لا علاقة له بالبرمجة الديناميكية. تخيل نظامين للكمبيوتر: واحد يسمى البادئ ، والثاني - النظام المستهدف. يحتاج البادئ إلى تنفيذ وظيفة
foo على النظام المستهدف ، ويمر هناك مجموعة معينة من المعلمات ، ويحصل على النتائج. بعد اكتشاف النظام المستهدف ، يجب على البادئ توفير جميع المعلومات اللازمة لتنفيذ الوظيفة بتنسيق يكون مفهوما لها. كحد أدنى ، سوف تتضمن هذه المعلومات اسم الوظيفة والمعلمات التي تم تمريرها. بعد تفريغ الطلب والتحقق من صحة المعلمات ، سيقوم النظام الهدف بتنفيذ وظيفة فو. بعد ذلك ، يجب أن يحزم النتيجة ، بما في ذلك أي أخطاء حدثت أثناء التنفيذ ، وإرسالها مرة أخرى إلى البادئ. أخيرًا ، يجب أن يكون البادئ قادرًا على فك النتائج وإخطار الهدف. نمط طلب الاستجابة هذا شائع جدًا ويصف على مستوى عال تشغيل أي آلية IPC تقريبًا.
DynamicMetaObject
لفهم كيفية تنفيذ DLR للنمط المقدم ، دعنا ننظر إلى إحدى الفئات المركزية لـ DLR:
DynamicMetaObject . نبدأ باستكشاف ثلاثة من الطرق الرئيسية الاثني عشر لهذا النوع:
- BindCreateInstance - إنشاء أو تنشيط كائن
- BindInvokeMember - استدعاء الأسلوب مغلفة
- BindInvoke - تنفيذ الكائن (كدالة)
عندما تحتاج إلى تنفيذ طريقة على نظام بعيد ، فأنت تحتاج أولاً إلى إنشاء مثيل من النوع. بالطبع ، ليست كل الأنظمة موجهة للكائنات ، لذا فإن مصطلح "مثيل" يمكن أن يكون استعارة. في الواقع ، يمكن تنفيذ الخدمة التي نحتاج إليها كمجموعة من الكائنات أو كحالة مفردة ، بحيث يمكن استخدام مصطلحي "التنشيط" أو "الاتصال" بنفس حق "المثيل".
الأطر الأخرى تتبع نفس النمط. على سبيل المثال ، يوفر COM وظيفة
CoCreateInstance لإنشاء كائنات. في .NET عن بُعد ، يمكنك استخدام الأسلوب
CreateInstance من فئة
System.Activator . يوفر DLR
DynamicMetaObject BindCreateInstance لأغراض مماثلة.
بعد استخدام الأسلوب
BindCreateInstance ، يمكن أن يكون
شيء تم إنشاؤه نوعًا يكشف عدة طرق.
يتم استخدام
الأسلوب metaobject BindInvokeMember لربط عملية يمكن استدعاء دالة. في الصورة أعلاه ، يمكن تمرير السلسلة foo كمعلمة للإشارة إلى الموثق بأنه يجب استدعاء طريقة بهذا الاسم. بالإضافة إلى ذلك ، يتم تضمين معلومات حول عدد الوسائط وأسمائها وعلامة خاصة تشير إلى الموثق ما إذا كان من الممكن تجاهل الحالة عند البحث عن عنصر مسمى مناسب. بعد كل شيء ، ليست كل اللغات حساسة لحالة الأحرف.
عندما يكون
شيء ما تم إرجاعه من
BindCreateInstance هو وظيفة واحدة فقط (أو المفوض) ، يتم استخدام الأسلوب BindInvoke. لتوضيح الصورة ، دعونا نلقي نظرة على الجزء الصغير التالي من الكود الديناميكي:
delegate void IntWriter(int n); void Main() { dynamic Write = new IntWriter(Console.WriteLine); Write(5); }
هذا الرمز ليس هو أفضل طريقة لطباعة الرقم 5 على وحدة التحكم. المطور الجيد لن يستخدم أي شيء مضيِّن. ومع ذلك ، يوضح هذا الرمز استخدام متغير ديناميكي تكون قيمته مفوضًا يمكن استخدامه كدالة. إذا قام نوع المفوض بتطبيق واجهة
IDynamicMetaObjectProvider ، فسيتم
استخدام أسلوب
BindInvoke من
DynamicMetaObject لربط العملية بالعمل الحقيقي. هذا لأن المحول البرمجي يتعرف على أن كائن
الكتابة الديناميكي يستخدم بناءً على ذلك كدالة. الآن خذ بعين الاعتبار جزء آخر من التعليمات البرمجية لفهم متى سيقوم المحول البرمجي بإنشاء
BindInvokeMember :
class Writer : IDynamicMetaObjectProvider { public void Write(int n) { Console.WriteLine(n); }
سأحذف تنفيذ الواجهة في هذا المثال الصغير ، لأن الأمر سيستغرق الكثير من التعليمات البرمجية لإثبات ذلك بشكل صحيح. في هذا المثال المختصر ، نطبق كائن تعريف ديناميكي مع بضعة أسطر من التعليمات البرمجية.
الشيء المهم الذي يجب فهمه هو أن المترجم يتعرف على أن
Writer.Write (7) هو عملية وصول إلى عنصر. ما نسميه عادةً "عامل التشغيل" في C # يسمى رسميًا "عامل الوصول لعضو النوع". سيقوم رمز DLR الذي تم إنشاؤه بواسطة برنامج التحويل البرمجي في هذه الحالة في نهاية الأمر باستدعاء
BindInvokeMember ، والذي سيمر فيه سلسلة الكتابة والمعلمة رقم 7 إلى العملية القادرة على إجراء المكالمة. باختصار ،
يتم استخدام
BindInvoke لاستدعاء كائن حيوي كدالة ، بينما
يتم استخدام
BindInvokeMember لاستدعاء أسلوب كعنصر كائن حيوي.
الوصول إلى الخصائص من خلال DynamicMetaObject
يمكن أن يكون من الأمثلة أعلاه أن المحول البرمجي يستخدم بناء جملة اللغة لتحديد عمليات ربط DLR التي يجب تنفيذها. إذا كنت تستخدم Visual Basic للعمل مع الكائنات الديناميكية ، فسيتم استخدام دلالاتها. بالطبع ، هناك حاجة إلى مشغل الوصول (نقطة) ليس فقط للوصول إلى الأساليب. يمكنك استخدامه للوصول إلى الخصائص. يوفر كائن التعريف DLR ثلاث طرق للوصول إلى خصائص الكائنات الديناميكية:
- BindGetMember - الحصول على قيمة العقار
- BindSetMember - تعيين قيمة الخاصية
- BindDeleteMember - حذف عنصر
يجب أن يكون الغرض من
BindGetMember و
BindSetMember واضحًا. خاصة الآن بعد أن عرفت مدى ارتباطها بكيفية عمل .NET مع الخصائص. عندما يحسب المحول البرمجي خصائص
get ("read") لكائن حيوي ، فإنه يستخدم استدعاء
BindGetMember . عندما يحسب المترجم مجموعة ("سجل") ، فإنه يستخدم
BindSetMember .
تمثيل كائن كصفيف
بعض الفئات عبارة عن حاويات لمثيلات من الأنواع الأخرى. DLR يعرف كيفية التعامل مع مثل هذه الحالات. يحتوي كل أسلوب كائن مصفوفة "موجه نحو الصفيف" على "فهرس" بادئة:
- BindGetIndex - الحصول على القيمة حسب الفهرس
- BindSetIndex - تعيين القيمة حسب الفهرس
- BindDeleteIndex - حذف قيمة حسب الفهرس
لفهم كيفية استخدام
BindGetIndex و
BindSetIndex ، تخيل
فئة مجمّع
JavaBridge يمكنها تحميل الملفات باستخدام فئات Java وتسمح لك باستخدامها من كود .NET دون أي صعوبات. يمكن استخدام مثل هذا المجمع لتحميل فئة Java الخاصة
بالعميل ، والتي تحتوي على بعض رموز ORM. يمكن استخدام كائن التعريف DLR لاستدعاء رمز ORM هذا من .NET بنمط C # الكلاسيكي. يوجد أدناه نموذج التعليمات البرمجية الذي يوضح كيفية عمل
JavaBridge في الممارسة:
JavaBridge java = new JavaBridge(); dynamic customers = java.Load("Customer.class"); dynamic Jason = customers["Bock"]; Jason.Balance = 17.34; customers["Wagner"] = new Customer("Bill");
نظرًا لأن الخطين الثالث والخامس يستخدمان عامل الوصول عن طريق الفهرس ([]) ، يتعرف المترجم على هذا ويستخدم
أساليب BindGetIndex و
BindSetIndex عند العمل مع كائن التعريف الذي تم إرجاعه من
JavaBridge . من المفهوم أن تطبيق هذه الأساليب على الكائن المرتجع سيطلب تنفيذ الطريقة من JVM من خلال استدعاء Java Remote Method (RMI). في هذا السيناريو ، يعمل DLR كجسر بين C # ولغة أخرى مع كتابة ثابتة. آمل أن يوضح هذا لماذا دعوت DLR "لغة اللغات".
أسلوب
BindDeleteMember ، تمامًا مثل
BindDeleteIndex ، غير مخصص للاستخدام من اللغات ذات الكتابة الثابتة مثل C # و Visual Basic ، حيث أنها لا تدعم المفهوم نفسه. ومع ذلك ، يمكنك الموافقة على "إزالة" بعض العمليات التي يتم التعبير عنها بواسطة اللغة ، إذا كان ذلك مفيدًا لك. على سبيل المثال ، يمكنك تطبيق BindDeleteMember كإلغاء عنصر بواسطة فهرس.
يحول والمشغلين
المجموعة الأخيرة من طرق metaobject DLR تدور حول التعامل مع المشغلين والتحولات.
- BindConvert - تحويل كائن إلى نوع آخر
- BindBinaryOperation - باستخدام عامل تشغيل ثنائي على اثنين من المعاملات
- BindUnaryOperation - باستخدام عامل أحادي على معامل واحد
يتم استخدام الأسلوب
BindConvert عندما يدرك المحول البرمجي أن الكائن يحتاج إلى تحويل إلى نوع معروف آخر. يحدث التحويل الضمني عندما يتم تعيين نتيجة استدعاء ديناميكي لمتغير بنوع ثابت. على سبيل المثال ، في المثال التالي C # ، يؤدي تعيين المتغير
y إلى استدعاء ضمني إلى
BindConvert :
dynamic x = 13; int y = x + 11;
دائمًا ما يتم استخدام
أساليب BindBinaryOperation و
BindUnaryOperation عند
مواجهة العمليات الحسابية ("+") أو الزيادات ("++"). في المثال أعلاه ، ستضيف إضافة المتغير الديناميكي
x إلى الثابت 11 طريقة
BindBinaryOperation . تذكر هذا المثال الصغير ، فنحن نستخدمه في القسم التالي لتفجير فئة DLR رئيسية أخرى تسمى CallSite.
إيفاد ديناميكي مع CallSite
إذا لم تتعدى مقدمة DLR الخاصة بك باستخدام الكلمة الأساسية
الديناميكية ، فربما لن تعرف أبدًا وجود CallSite في .NET Framework. يوجد هذا النوع المتواضع ، المعروف رسميًا باسم
CallSite < T > ، في
مساحة اسم System.Runtime.CompilerServices . هذا هو "مصدر الطاقة" للبرمجة metaprogramming: إنه مليء بجميع أنواع أساليب التحسين التي تجعل كود .NET الديناميكي سريعًا وفعالًا.
سأذكر جوانب أداء
CallSite < T > في نهاية المقالة.
معظم ما يقوم به CallSite في كود .NET الديناميكي يتضمن إنشاء وتجميع التعليمات البرمجية في وقت التشغيل. من المهم ملاحظة أن فئة
CallSite < T > تقع في مساحة الاسم التي تحتوي على الكلمتين "
Runtime " و "
CompilerServices ". إذا كانت DLR هي "لغة اللغات" ، فإن
CallSite < T > هي واحدة من أهم التركيبات النحوية. دعنا ننظر إلى مثالنا من القسم السابق مرة أخرى للتعرف على CallSite وكيف يقوم المترجم بتضمينها في الكود.
dynamic x = 13; int y = x + 11;
كما تعلمون بالفعل ، سيتم استدعاء أساليب
BindBinaryOperaion و
BindConvert لتنفيذ هذا الرمز. بدلاً من إظهار قائمة طويلة بكود MSIL المفكك الذي تم إنشاؤه بواسطة المترجم ، قمت بعمل رسم تخطيطي:

تذكر أن المحول البرمجي يستخدم بناء جملة اللغة لتحديد طرق الكتابة الديناميكية المراد تنفيذها. في مثالنا ، يتم تنفيذ عمليتين: إضافة المتغير
x إلى الرقم (
Site2 )
وإعطاء النتيجة إلى int (
Site1 ). يتحول كل من هذه الإجراءات إلى CallSite ، والتي يتم تخزينها في حاوية خاصة. كما ترى في المخطط ، يتم إنشاء CallSites بالترتيب العكسي ، ولكن يتم استدعاؤها بالطريقة الصحيحة.
في الشكل ، يمكنك رؤية أن أساليب
metaobject يتم استدعاء
BindConvert و
BindBinaryOperation مباشرة قبل العمليات "إنشاء CallSite1" و "إنشاء CallSite2". ومع ذلك ، يتم تنفيذ العمليات المرتبطة فقط في النهاية. آمل أن يساعدك التصور على فهم أن أساليب الربط ودعوتهم هي عمليات مختلفة في سياق DLR. علاوة على ذلك ، يحدث الربط مرة واحدة فقط ، بينما تحدث المكالمة عدة مرات حسب الحاجة ، مع إعادة استخدام CallSites التي تمت تهيئتها بالفعل لتحسين الأداء.
اتبع الطريق السهل
في صميم DLR ، تُستخدم أشجار التعبير لإنشاء وظائف مرتبطة بأساليب الربط الاثني عشر المذكورة أعلاه. يواجه العديد من المطورين باستمرار أشجار التعبير باستخدام LINQ ، لكن قلة قليلة منهم فقط لديهم خبرة عميقة بما يكفي لتنفيذ عقد
IDynamicMetaObjectProvider بالكامل. لحسن الحظ ، يحتوي .NET Framework على فئة أساسية تسمى
DynamicObject تتولى معظم العمل.
لإنشاء فئة ديناميكية خاصة بك ، كل ما عليك فعله هو وراثة
DynamicObject وتنفيذ الطرق الاثني عشر التالية:
- TryCreateInstance
- TryInvokeMember
- TryInvoke
- TryGetMember
- TrySetMember
- TryDeleteMember
- TryGetIndex
- TrySetIndex
- TryDeleteIndex
- TryConvert
- TryBinaryOperation
- TryUnaryOperation
هل تبدو أسماء الطرق مألوفة؟ يجب عليك ، لأنك انتهيت للتو من دراسة عناصر فئة Abstract
DynamicMetaObject ، والتي تتضمن أساليب مثل
BindCreateInstance و
BindInvoke . توفر الفئة
DynamicMetaObject تطبيقًا لـ
IDynamicMetaObjectProvider ، والذي يُرجع
DynamicMetaObject من
طريقته فقط. العمليات المرتبطة بالتطبيق الأساسي لكائن التعريف ببساطة تفويض مكالماتهم إلى الطرق التي تبدأ ب "حاول" على مثيل
DynamicObject . كل ما عليك القيام به هو التحميل الزائد لأساليب مثل
TryGetMember و
TrySetMember في فئة موروثة من
DynamicObject ، في حين أن كائن التعريف سيأخذ كل العمل القذر مع أشجار التعبير.
التخزين المؤقت
[يمكنك قراءة المزيد حول التخزين المؤقت في مقالتي السابقة على DLR ]أكبر مصدر قلق عند العمل مع اللغات الديناميكية للمطورين هو الأداء. DLR تتخذ تدابير استثنائية لتبديد هذه التجارب. ذكرت باختصار حقيقة أن
CallSite < T > يتواجد في مساحة اسم تسمى
System.Runtime.CompilerServices . في نفس مساحة الاسم ، تكمن عدة فئات أخرى توفر التخزين المؤقت متعدد المستويات. باستخدام هذه الأنواع ، تطبق DLR ثلاثة مستويات رئيسية للتخزين المؤقت لتسريع العمليات الديناميكية:
- مخبأ عالمي
- ذاكرة التخزين المؤقت المحلية
- متعدد الأشكال مندوب ذاكرة التخزين المؤقت
يتم استخدام ذاكرة التخزين المؤقت لتجنب تبذير الموارد غير الضروري لإنشاء روابط لموقع CallSite محدد. إذا
تم تمرير كائنين من
سلسلة type إلى طريقة ديناميكية تُرجع
int ،
فسيحفظ التخزين المؤقت العام أو المحلي الرابط الناتج. هذا سوف تبسيط المكالمات اللاحقة إلى حد كبير.
تسمى ذاكرة التخزين المؤقت للمفوض ، والتي تقع داخل CallSite نفسه ، متعددة الأشكال ، لأن هؤلاء المفوضين يمكن أن يتخذوا أشكالًا مختلفة بناءً على التعليمات البرمجية الديناميكية التي تم تنفيذها وأي قواعد من ذاكرة التخزين المؤقت الأخرى تم استخدامها لإنشاء هذه التخزينات. تسمى ذاكرة التخزين المؤقت للمفوض أيضًا أحيانًا ذاكرة التخزين المؤقت المضمّنة. سبب استخدام هذا المصطلح هو أن التعبيرات التي تم إنشاؤها بواسطة DLR والمجلدات الخاصة بها يتم تحويلها إلى رمز MSIL الذي يمر عبر تجميع JIT ، مثل أي رمز .NET آخر. يحدث التجميع في وقت التشغيل بالتزامن مع التنفيذ "العادي" للبرنامج. من الواضح أن تحويل الشفرة الديناميكية أثناء التنقل إلى رمز MSIL مترجم أثناء تنفيذ البرنامج يمكن أن يؤثر بشكل كبير على أداء التطبيق ، لذلك آليات التخزين المؤقت ضرورية.