هل تم هزيمة هدف التقسيم الواضح من خلال مشاركة الكثير من معلومات النوع بين المكتبات، ربما تحتاج إلى تخزين فعال للبيانات المكتوبة بقوة، ولكن سيكون ذلك مكلفًا للغاية إذا كنت بحاجة إلى تحديث مخطط قاعدة البيانات الخاصة بك في كل مرة يتطور فيها نموذج الكائن، فهل ستفعل ذلك؟
هل تحتاج إلى استنتاج مخطط النوع في وقت التشغيل؟ هل تحتاج إلى تسليم مكونات تقبل كائنات المستخدم العشوائية والتعاملمعها
بطريقة ذكية؟ هل تريد أن يتمكن مترجم المكتبة من إخبارك بأنواعها برمجيًا؟
للحفاظ على هياكل البيانات المكتوبة بقوة مع زيادة مرونة وقت التشغيل إلى الحد الأقصى، قد ترغب على الأرجح في التفكير في التفكير وكيف يمكنه تحسين برنامجك. في هذا العمود، سأستكشف مساحة الاسم System.Reflection في Microsoft .NET Framework وكيف يمكن أن تفيد تجربة التطوير لديك. سأبدأ ببعض الأمثلة البسيطة وأنتهي بكيفية التعامل مع مواقف التسلسل في العالم الحقيقي. على طول الطريق، سأوضح كيف يعمل الانعكاس وCodeDom معًا للتعامل بكفاءة مع بيانات وقت التشغيل.
قبل أن أخوض في System.Reflection، أود مناقشة البرمجة الانعكاسية بشكل عام. أولاً، يمكن تعريف الانعكاس على أنه أي وظيفة يوفرها نظام برمجة يسمح للمبرمجين بفحص كيانات التعليمات البرمجية ومعالجتها دون معرفة مسبقة بهويتهم أو بنيتهم الرسمية. هناك الكثير مما يجب تغطيته في هذا القسم، لذا سأتناوله واحدًا تلو الآخر.
أولاً، ما الذي يوفره التفكير؟ وماذا يمكنك أن تفعل به؟ أميل إلى تقسيم المهام النموذجية التي تتمحور حول التفكير إلى فئتين: الفحص والتلاعب. يتطلب التفتيش تحليل الكائنات والأنواع لجمع معلومات منظمة حول تعريفها وسلوكها. وبصرف النظر عن بعض الأحكام الأساسية، غالبا ما يتم ذلك دون أي معرفة مسبقة بها. (على سبيل المثال، في .NET Framework، يرث كل شيء من System.Object، وغالبًا ما تكون الإشارة إلى نوع الكائن هي نقطة البداية العامة للتفكير.)
تستدعي العمليات التعليمات البرمجية ديناميكيًا باستخدام المعلومات المجمعة من خلال الفحص، أو إنشاء مثيلات جديدة، أو حتى يمكن إعادة هيكلة الأنواع والكائنات ديناميكيًا بسهولة. إحدى النقاط المهمة التي يجب مراعاتها هي أنه بالنسبة لمعظم الأنظمة، يؤدي التعامل مع الأنواع والكائنات في وقت التشغيل إلى تدهور الأداء مقارنةً بإجراء العمليات المكافئة بشكل ثابت في التعليمات البرمجية المصدر. تعد هذه مقايضة ضرورية بسبب الطبيعة الديناميكية للانعكاس، ولكن هناك العديد من النصائح وأفضل الممارسات لتحسين أداء الانعكاس (راجع msdn.microsoft.com/msdnmag/issues/05 للحصول على مزيد من المعلومات المتعمقة حول تحسين الأداء استخدام الانعكاس /07/الانعكاس).
إذن، ما هو هدف الانعكاس؟ ما الذي يقوم المبرمج بفحصه ومعالجته بالفعل؟ في تعريفي للانعكاس، استخدمت المصطلح الجديد "الكيان الكودي" للتأكيد على حقيقة أنه من وجهة نظر المبرمج، فإن تقنيات الانعكاس تطمس أحيانًا الخطوط الفاصلة بين بعضها البعض. الأشياء والأنواع التقليدية. على سبيل المثال، قد تكون المهمة النموذجية التي تتمحور حول الانعكاس هي:
البدء بمقبض للكائن O واستخدام الانعكاس للحصول على مؤشر للتعريف المرتبط به (النوع T).
افحص النوع T واحصل على مؤشر لطريقته M.
طريقة الاتصال M لكائن آخر O' (من النوع T أيضًا).
لاحظ أنني أقوم بالتنقل من مثيل واحد إلى نوعه الأساسي، ومن هذا النوع إلى إحدى الطرق، ثم أستخدم مقبض الطريقة لاستدعائه في مثيل آخر - من الواضح أن هذا يستخدم برمجة C# التقليدية في الكود المصدري ولا يمكن للتكنولوجيا تحقيق ذلك. بعد مناقشة System.Reflection الخاص بـ .NET Framework أدناه، سأشرح هذا الموقف مرة أخرى بمثال ملموس.
توفر بعض لغات البرمجة الانعكاس بشكل أصلي من خلال بناء الجملة، بينما توفره الأنظمة الأساسية والأطر الأخرى (مثل .NET Framework) كمكتبة نظام. بغض النظر عن كيفية توفير الانعكاس، فإن إمكانيات استخدام تكنولوجيا الانعكاس في موقف معين معقدة للغاية. تعتمد قدرة نظام البرمجة على توفير التفكير على عوامل عديدة: هل يستفيد المبرمج بشكل جيد من ميزات لغة البرمجة للتعبير عن مفاهيمه؟ هل يقوم المترجم بتضمين معلومات منظمة كافية (بيانات وصفية) في المخرجات لتسهيل التحليل المستقبلي؟ التفسير؟ هل يوجد نظام فرعي لوقت التشغيل أو مترجم مضيف يستوعب هذه البيانات الوصفية؟ هل تقدم مكتبة النظام الأساسي نتائج هذا التفسير بطريقة مفيدة للمبرمجين
إذا كان لديك نظام معقد وموجه للكائنات
؟تظهر كوظيفة بسيطة على طراز C في الكود، ولا توجد بنية بيانات رسمية، فمن الواضح أنه من المستحيل لبرنامجك أن يستنتج ديناميكيًا أن مؤشر متغير معين v1 يشير إلى مثيل كائن من نوع معين T . لأنه بعد كل شيء، النوع T هو مفهوم في رأسك، ولا يظهر بشكل صريح في بيانات البرمجة الخاصة بك. ولكن إذا كنت تستخدم لغة موجهة للكائنات أكثر مرونة (مثل C#) للتعبير عن البنية المجردة للبرنامج، وتقديم مفهوم النوع T مباشرة، فسيقوم المترجم بتحويل فكرتك إلى شيء يمكن تمريره لاحقًا عبر المنطق المناسب لفهم النموذج، كما هو منصوص عليه في وقت تشغيل اللغة العامة (CLR) أو بعض مترجم اللغة الديناميكي.
هل الانعكاس عبارة عن تقنية ديناميكية في وقت التشغيل؟ هناك عدة مرات خلال دورة التطوير والتنفيذ يكون فيها الانعكاس متاحًا ومفيدًا للمطورين. يتم تنفيذ بعض لغات البرمجة من خلال مترجمين مستقلين يقومون بتحويل التعليمات البرمجية عالية المستوى مباشرة إلى تعليمات يمكن للجهاز فهمها. يتضمن ملف الإخراج فقط مدخلات مجمعة، ولا يحتوي وقت التشغيل على منطق داعم لقبول الكائنات غير الشفافة وتحليل تعريفاتها ديناميكيًا. هذا هو الحال تمامًا مع العديد من مترجمات لغة C التقليدية. نظرًا لوجود القليل من المنطق الداعم في الهدف القابل للتنفيذ، لا يمكنك القيام بالكثير من التفكير الديناميكي، لكن المترجمين يوفرون انعكاسًا ثابتًا من وقت لآخر - على سبيل المثال، يسمح عامل التشغيل typeof الموجود في كل مكان للمبرمجين بالتحقق من معرفات النوع في وقت الترجمة.
الوضع المختلف تمامًا هو أن لغات البرمجة المفسرة يتم تنفيذها دائمًا من خلال العملية الرئيسية (عادةً ما تندرج لغات البرمجة النصية ضمن هذه الفئة). وبما أن التعريف الكامل للبرنامج متاح (كرمز مصدر الإدخال)، بالإضافة إلى التنفيذ الكامل للغة (كالمترجم نفسه)، فإن جميع التقنيات اللازمة لدعم التحليل الذاتي موجودة. توفر هذه اللغة الديناميكية في كثير من الأحيان قدرات تفكير شاملة، بالإضافة إلى مجموعة غنية من الأدوات للتحليل الديناميكي ومعالجة البرامج.
يقع .NET Framework CLR واللغات المضيفة له مثل C# في المنتصف. يتم استخدام المترجم لتحويل كود المصدر إلى IL وبيانات التعريف، وهذا الأخير هو مستوى أقل أو أقل "منطقية" من كود المصدر، ولكنه لا يزال يحتفظ بالكثير من البنية المجردة ومعلومات النوع. بمجرد بدء تشغيل CLR واستضافته لهذا البرنامج، يمكن لمكتبة System.Reflection التابعة لمكتبة الفئة الأساسية (BCL) استخدام هذه المعلومات وإرجاع معلومات حول نوع الكائن ونوع الأعضاء وتوقيعات الأعضاء وما إلى ذلك. بالإضافة إلى ذلك، يمكنه أيضًا دعم المكالمات، بما في ذلك المكالمات المتأخرة.
الانعكاس في .NET
للاستفادة من الانعكاس عند البرمجة باستخدام .NET Framework، يمكنك استخدام مساحة الاسم System.Reflection. توفر مساحة الاسم هذه فئات تحتوي على العديد من مفاهيم وقت التشغيل، مثل التجميعات والوحدات النمطية والأنواع والأساليب والمنشئات والحقول والخصائص. يوضح الجدول في الشكل 1 كيفية ربط الفئات الموجودة في System.Reflection بنظيراتها في وقت التشغيل المفاهيمي.
على الرغم من أهميته، يتم استخدام System.Reflection.Assembly وSystem.Reflection.Module بشكل أساسي لتحديد موقع التعليمات البرمجية الجديدة وتحميلها في وقت التشغيل. في هذا العمود، لن أناقش هذه الأجزاء وأفترض أن جميع التعليمات البرمجية ذات الصلة قد تم تحميلها بالفعل.
لفحص التعليمات البرمجية المحملة ومعالجتها، يكون النمط النموذجي هو System.Type بشكل أساسي. عادةً، تبدأ بالحصول على مثيل System.Type لفئة وقت التشغيل محل الاهتمام (عبر Object.GetType). يمكنك بعد ذلك استخدام أساليب مختلفة من System.Type لاستكشاف تعريف النوع في System.Reflection والحصول على مثيلات الفئات الأخرى. على سبيل المثال، إذا كنت مهتمًا بأسلوب معين وتريد الحصول على مثيل System.Reflection.MethodInfo لهذا الأسلوب (ربما من خلال Type.GetMethod). وبالمثل، إذا كنت مهتمًا بحقل ما وتريد الحصول على مثيل System.Reflection.FieldInfo لهذا الحقل (ربما من خلال Type.GetField).
بمجرد حصولك على جميع كائنات مثيلات الانعكاس الضرورية، يمكنك المتابعة باتباع خطوات الفحص أو المعالجة حسب الحاجة. عند التحقق، يمكنك استخدام خصائص وصفية مختلفة في الفئة العاكسة للحصول على المعلومات التي تحتاجها (هل هذا نوع عام؟ هل هذه طريقة مثيل؟). عند التشغيل، يمكنك استدعاء الأساليب وتنفيذها ديناميكيًا، وإنشاء كائنات جديدة عن طريق استدعاء المنشئات، وما إلى ذلك.
التحقق من الأنواع والأعضاء
دعنا ننتقل إلى بعض التعليمات البرمجية ونستكشف كيفية التحقق باستخدام الانعكاس الأساسي. سأركز على تحليل النوع. بدءًا بالكائن، سأقوم باسترداد نوعه ثم فحص بعض الأعضاء المثيرين للاهتمام (انظر الشكل 2).
أول شيء يجب ملاحظته هو أنه في تعريف الفصل، يبدو للوهلة الأولى أن هناك مساحة أكبر بكثير لوصف الأساليب مما كنت أتوقع. من أين تأتي هذه الأساليب الإضافية؟ أي شخص على دراية بالتسلسل الهرمي لكائنات .NET Framework سوف يتعرف على هذه الأساليب الموروثة من كائن الفئة الأساسية المشتركة نفسه. (في الواقع، استخدمت Object.GetType لأول مرة لاسترداد نوعه.) بالإضافة إلى ذلك، يمكنك رؤية وظيفة getter الخاصة بالخاصية. الآن، ماذا لو كنت بحاجة فقط إلى الوظائف المحددة بوضوح في MyClass نفسها؟ وبعبارة أخرى، كيف يمكنك إخفاء الوظائف الموروثة؟ أو ربما تحتاج فقط إلى وظائف المثيل المحددة بوضوح؟
فقط قم بإلقاء نظرة على MSDN وستعرف لقد وجدت أن الجميع على استعداد لاستخدام الطريقة الثانية المحملة بشكل زائد لـ GetMethods، والتي تقبل معلمة BindingFlags. من خلال الجمع بين قيم مختلفة من تعداد BindingFlags، يمكنك الحصول على دالة تُرجع فقط المجموعة الفرعية المطلوبة من الأساليب. استبدل استدعاء GetMethods بـ:
GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly |BindingFlags.Public)
ونتيجة لذلك، تحصل على الإخراج التالي (لاحظ أنه لا توجد وظائف مساعدة ثابتة ووظائف موروثة من System.Object).
المثال التوضيحي للانعكاس 1
اسم النوع:
اسم الأسلوب MyClass:
اسم الأسلوب MyMethod1:
اسم الأسلوب MyMethod2: get_MyProperty
اسم الخاصية: MyProperty
ماذا لو كنت تعرف اسم النوع (مؤهل بالكامل) والأعضاء مسبقًا كيف يمكنك إنجاز الاسترداد من نوع التعداد إلى النوع التحويل؟ باستخدام الكود الموجود في المثالين الأولين، لديك بالفعل المكونات الأساسية لتنفيذ متصفح فئة بدائية. يمكنك العثور على كيان وقت التشغيل بالاسم ثم تعداد خصائصه المتنوعة ذات الصلة.
استدعاء التعليمات البرمجية ديناميكيًا
لقد حصلت حتى الآن على مقابض لكائنات وقت التشغيل (مثل الأنواع والأساليب) لأغراض وصفية فقط، مثل طباعة أسمائها. ولكن كيف يمكنك فعل المزيد؟ كيف يمكنك بالفعل استدعاء الأسلوب؟
بعض النقاط الرئيسية في هذا المثال هي: أولاً، يتم استرداد مثيل System.Type من مثيل MyClass، mc1، ثم يتم استرداد مثيل MethodInfo من؟ هذا النوع. أخيرًا، عند استدعاء MethodInfo، يتم ربطه بمثيل MyClass (mc2) آخر عن طريق تمريره كمعلمة أولى للاستدعاء.
كما ذكرنا سابقًا، يطمس هذا المثال التمييز بين الأنواع واستخدام الكائنات الذي تتوقع رؤيته في التعليمات البرمجية المصدر. منطقيًا، يمكنك استرداد مؤشر أسلوب ثم استدعاء الأسلوب كما لو كان ينتمي إلى كائن مختلف. بالنسبة للمبرمجين الذين هم على دراية بلغات البرمجة الوظيفية، قد يكون هذا أمرًا سهلاً؛ ولكن بالنسبة للمبرمجين الذين هم على دراية بـ C# فقط، قد لا يكون من البديهي الفصل بين تنفيذ الكائن وإنشاء مثيل له.
تجميع كل ذلك معًا
لقد ناقشت حتى الآن المبادئ الأساسية للتحقق والاتصال، والآن سأجمعها مع أمثلة ملموسة. تخيل أنك تريد تقديم مكتبة بوظائف مساعدة ثابتة يجب أن تتعامل مع الكائنات. لكن في وقت التصميم، ليس لديك أي فكرة عن أنواع هذه الكائنات! يعتمد الأمر على تعليمات المتصل بالوظيفة حول الطريقة التي يريد بها استخراج معلومات ذات معنى من هذه الكائنات. ستقبل الوظيفة مجموعة من الكائنات واصف سلسلة للطريقة. سيتم بعد ذلك التكرار عبر المجموعة، واستدعاء أساليب كل كائن، وتجميع قيم الإرجاع مع بعض الوظائف.
في هذا المثال، سأعلن عن بعض القيود. أولاً، لن تقبل الطريقة الموصوفة بواسطة معلمة السلسلة (والتي يجب تنفيذها بواسطة النوع الأساسي لكل كائن) أي معلمات وستُرجع عددًا صحيحًا. سيتم تكرار الكود من خلال مجموعة الكائنات، واستدعاء الطريقة المحددة، وحساب متوسط جميع القيم تدريجيًا. أخيرًا، نظرًا لأن هذا ليس رمز إنتاج، فلا داعي للقلق بشأن التحقق من صحة المعلمة أو تجاوز عدد صحيح عند الجمع.
عند تصفح نموذج التعليمات البرمجية، يمكنك أن ترى أن الاتفاقية بين الوظيفة الرئيسية والمساعد الثابت ComputeAverage لا تعتمد على أي معلومات عن النوع بخلاف الفئة الأساسية المشتركة للكائن نفسه. بمعنى آخر، يمكنك تغيير نوع وبنية الكائن الذي يتم نقله تمامًا، ولكن طالما يمكنك دائمًا استخدام سلسلة لوصف طريقة تُرجع عددًا صحيحًا، فإن ComputeAverage سيعمل بشكل جيد. هناك
مشكلة رئيسية يجب ملاحظتها مخفي في المثال الأخير المتعلق بـ MethodInfo (انعكاس عام). لاحظ أنه في حلقة foreach الخاصة بـ ComputeAverage، تقوم التعليمات البرمجية فقط بالحصول على معلومات MethodInfo من الكائن الأول في المجموعة ثم ربطها باستدعاء كافة الكائنات اللاحقة. كما يظهر من الترميز، فإنه يعمل بشكل جيد - وهذا مثال بسيط على التخزين المؤقت لـ MethodInfo. ولكن هناك قيود أساسية هنا. لا يمكن استدعاء مثيل MethodInfo إلا من خلال مثيل من نفس النوع الهرمي للكائن الذي يسترده. وهذا ممكن لأنه يتم تمرير مثيلات IntReturner وSonOfIntReturner (الموروثة من IntReturner).
في نموذج التعليمات البرمجية، تم تضمين فئة تسمى EnemyOfIntReturner، والتي تطبق نفس البروتوكول الأساسي مثل الفئتين الأخريين، ولكنها لا تشترك في أي أنواع مشتركة شائعة. بمعنى آخر، الواجهات متكافئة منطقيًا، لكن لا يوجد تداخل على مستوى الكتابة. لاستكشاف استخدام MethodInfo في هذه الحالة، حاول إضافة كائن آخر إلى المجموعة، واحصل على مثيل عبر "new EnemyOfIntReturner(10)"، وقم بتشغيل المثال مرة أخرى. سوف تواجه استثناءً يشير إلى أنه لا يمكن استخدام MethodInfo لاستدعاء الكائن المحدد لأنه لا علاقة له على الإطلاق بالنوع الأصلي الذي تم الحصول على MethodInfo منه (على الرغم من أن اسم الطريقة والبروتوكول الأساسي متكافئان). لجعل التعليمات البرمجية الخاصة بك جاهزة للإنتاج، يجب أن تكون مستعدًا لمواجهة هذا الموقف.
قد يكون الحل المحتمل هو تحليل أنواع جميع الكائنات الواردة بنفسك، مع الاحتفاظ بتفسير التسلسل الهرمي للأنواع المشتركة (إن وجد). إذا كان نوع الكائن التالي مختلفًا عن أي تسلسل هرمي معروف للأنواع، فيجب الحصول على معلومات جديدة وتخزينها. الحل الآخر هو التقاط TargetException وإعادة الحصول على مثيل MethodInfo. كلا الحلين المذكورين هنا لهما إيجابيات وسلبيات. كتب جويل بوبار مقالة ممتازة لعدد مايو 2007 من هذه المجلة حول أداء التخزين المؤقت والانعكاس لـMethodInfo، وهو ما أوصي به بشدة.
نأمل أن يوضح هذا المثال إضافة انعكاس إلى تطبيق أو إطار عمل لإضافة المزيد من المرونة للتخصيص أو القابلية للتوسعة في المستقبل. من المسلم به أن استخدام الانعكاس يمكن أن يكون مرهقًا بعض الشيء مقارنة بالمنطق المكافئ في لغات البرمجة الأصلية. إذا كنت تشعر أن إضافة الارتباط المتأخر المستند إلى الانعكاس إلى التعليمات البرمجية الخاصة بك أمر مرهق للغاية بالنسبة لك أو لعملائك (بعد كل شيء، فهم يحتاجون إلى مراعاة أنواعهم ورموزهم في إطار العمل الخاص بك بطريقة ما)، فقد يكون ذلك ضروريًا فقط في مرونة الإشراف لتحقيق بعض التوازن.
معالجة فعالة للكتابة من أجل التسلسل
الآن بعد أن قمنا بتغطية المبادئ الأساسية لانعكاس .NET من خلال عدة أمثلة، دعنا نلقي نظرة على موقف حقيقي. إذا كان برنامجك يتفاعل مع الأنظمة الأخرى من خلال خدمات الويب أو غيرها من تقنيات الاتصال عن بعد خارج العملية، فمن المحتمل أنك واجهت مشكلات في التسلسل. يقوم التسلسل بشكل أساسي بتحويل الكائنات النشطة التي تشغل الذاكرة إلى تنسيق بيانات مناسب للنقل عبر الإنترنت أو تخزين القرص.
توفر مساحة الاسم System.Xml.Serialization في .NET Framework محرك تسلسل قوي مع XmlSerializer، والذي يمكنه أخذ أي كائن مُدار وتحويله إلى XML (يمكن أيضًا تحويل بيانات XML مرة أخرى إلى مثيل كائن مكتوب في المستقبل، هذه العملية يسمى إلغاء التسلسل). تعد فئة XmlSerializer برنامجًا قويًا وجاهزًا للمؤسسات وسيكون خيارك الأول إذا واجهت مشكلات في التسلسل في مشروعك. ولكن للأغراض التعليمية، دعونا نستكشف كيفية تنفيذ التسلسل (أو مثيلات أخرى مماثلة للتعامل مع نوع وقت التشغيل).
ضع في اعتبارك ما يلي: أنت تقدم إطارًا يأخذ مثيلات الكائنات لأنواع المستخدمين العشوائية ويحولها إلى بعض تنسيقات البيانات الذكية. على سبيل المثال، افترض أن لديك كائنًا مقيمًا في الذاكرة من النوع "العنوان" كما هو موضح أدناه:
(الكود الكاذب)
classAddress
{
معرف العنوان؛
شارع سترينج، المدينة؛
حالة نوع الحالة؛
ZipCodeType ZipCode;
}
كيفية إنشاء تمثيل بيانات مناسب لاستخدامه لاحقًا ربما يؤدي تقديم نص بسيط إلى حل هذه المشكلة:
العنوان: 123
Street: 1 Microsoft Way
City: Redmond
State: WA
Zip: 98052
إذا كانت البيانات الرسمية التي تحتاج إلى تحويل مفهومة تمامًا؟ اكتب مسبقًا (على سبيل المثال، عند كتابة الكود بنفسك)، تصبح الأمور بسيطة جدًا:
foreach(Address a in AddressList)
{
Console.WriteLine("العنوان:{0}"، a.ID)؛
Console.WriteLine("tStreet:{0}"، a.Street);
... // وهكذا
}
ومع ذلك، يمكن أن تصبح الأمور مثيرة للاهتمام حقًا إذا كنت لا تعرف مسبقًا أنواع البيانات التي ستواجهها في وقت التشغيل. كيف تكتب رمز الإطار العام مثل هذا
MyFramework.TranslateObject(object input, MyOutputWriter)
أولاً، عليك أن تقرر أي نوع من الأعضاء مفيد للتسلسل. تتضمن الاحتمالات التقاط أعضاء من نوع معين فقط، مثل أنواع النظام البدائية، أو توفير آلية لمؤلفي النوع للإشارة إلى الأعضاء الذين يحتاجون إلى إجراء تسلسل، مثل استخدام الخصائص المخصصة كعلامات على أعضاء النوع). يمكنك فقط التقاط أعضاء من نوع معين، مثل أنواع النظام البدائية، أو يمكن لمؤلف النوع تحديد الأعضاء الذين يحتاجون إلى إجراء تسلسل (ربما باستخدام خصائص مخصصة كعلامات على أعضاء النوع).
بمجرد توثيق أعضاء بنية البيانات التي تحتاج إلى تحويل، ما عليك القيام به بعد ذلك هو كتابة المنطق لتعدادهم واسترجاعهم من الكائنات الواردة. يقوم الانعكاس بالمهمة الصعبة هنا، مما يسمح لك بالاستعلام عن كل من هياكل البيانات وقيم البيانات.
من أجل التبسيط، دعونا نصمم محرك تحويل خفيف الوزن يأخذ كائنًا، ويحصل على جميع قيم الملكية العامة الخاصة به، ويحولها إلى سلاسل عن طريق استدعاء ToString مباشرة، ثم يقوم بإجراء تسلسل للقيم. بالنسبة لكائن معين يسمى "الإدخال"، تكون الخوارزمية تقريبًا كما يلي:
استدعاء input.GetType لاسترداد مثيل System.Type، الذي يصف البنية الأساسية للإدخال.
استخدم Type.GetProperties والمعلمة BindingFlags المناسبة لاسترداد الخصائص العامة كمثيلات PropertyInfo.
يتم استرداد الخصائص كأزواج قيمة مفتاح باستخدام PropertyInfo.Name وPropertyInfo.GetValue.
قم باستدعاء Object.ToString على كل قيمة لتحويلها (بطريقة أساسية) إلى تنسيق سلسلة.
قم بتعبئة اسم نوع الكائن ومجموعة أسماء الخصائص وقيم السلسلة في تنسيق التسلسل الصحيح.
تعمل هذه الخوارزمية على تبسيط الأمور بشكل كبير بينما تلتقط أيضًا نقطة أخذ بنية بيانات وقت التشغيل وتحويلها إلى بيانات ذاتية الوصف. ولكن هناك مشكلة: الأداء. كما ذكرنا من قبل، يعد الانعكاس مكلفًا للغاية لكل من معالجة الكتابة واسترجاع القيمة. في هذا المثال، أقوم بإجراء تحليل كامل للنوع على كل مثيل من النوع المقدم.
ماذا لو كان من الممكن بطريقة أو بأخرى التقاط فهمك لبنية النوع أو الحفاظ عليه حتى تتمكن من استعادته لاحقًا بسهولة والتعامل بكفاءة مع المثيلات الجديدة من هذا النوع؛ بمعنى آخر، انتقل إلى الخطوة رقم 3 في خوارزمية المثال الخبر هو أنه من الممكن القيام بذلك باستخدام الميزات الموجودة في .NET Framework. بمجرد فهم بنية بيانات نوع ما، يمكنك استخدام CodeDom لإنشاء تعليمات برمجية ترتبط ببنية البيانات هذه ديناميكيًا. يمكنك إنشاء مجموعة مساعدة تحتوي على فئة مساعدة وأساليب تشير إلى النوع الوارد وتصل إلى خصائصه مباشرةً (مثل أي خاصية أخرى في التعليمات البرمجية المُدارة)، لذا فإن التحقق من الكتابة يؤثر على الأداء مرة واحدة فقط.
الآن سوف أقوم بإصلاح هذه الخوارزمية. نوع جديد:
احصل على مثيل System.Type المطابق لهذا النوع.
استخدم أدوات الوصول System.Type المتنوعة لاسترداد المخطط (أو على الأقل المجموعة الفرعية من المخطط المفيدة للتسلسل)، مثل أسماء الخصائص، وأسماء الحقول، وما إلى ذلك.
استخدم معلومات المخطط لإنشاء تجميع مساعد (عبر CodeDom) يرتبط بالنوع الجديد ويتعامل مع المثيلات بكفاءة.
استخدم التعليمات البرمجية في التجميع المساعد لاستخراج بيانات المثيل.
تسلسل البيانات حسب الحاجة.
بالنسبة لجميع البيانات الواردة من نوع معين، يمكنك الانتقال إلى الخطوة رقم 4 والحصول على تحسين كبير في الأداء من خلال التحقق بشكل صريح من كل مثيل.
لقد قمت بتطوير مكتبة تسلسل أساسية تسمى SimpleSerialization والتي تنفذ هذه الخوارزمية باستخدام الانعكاس وCodeDom (يمكن تنزيلهما في هذا العمود). المكون الرئيسي هو فئة تسمى SimpleSerializer، والتي تم إنشاؤها بواسطة المستخدم باستخدام مثيل System.Type. في المُنشئ، يقوم مثيل SimpleSerializer الجديد بتحليل النوع المحدد وإنشاء تجميع مؤقت باستخدام الفئات المساعدة. ترتبط الفئة المساعدة بإحكام بنوع البيانات المحدد وتتعامل مع المثيل كما لو كنت تكتب الكود مع معرفة مسبقة كاملة بالنوع.
تحتوي فئة SimpleSerializer على التخطيط التالي:
فئة SimpleSerializer
{
فئة عامة SimpleSerializer (اكتب نوع البيانات)؛
تسلسل الفراغ العام (إدخال الكائن ، كاتب SimpleDataWriter) ؛
}
إنه أمر مذهل ببساطة! يقوم المنشئ بالمهمة الصعبة: فهو يستخدم الانعكاس لتحليل بنية الكتابة ثم يستخدم CodeDom لإنشاء التجميع المساعد. تعد فئة SimpleDataWriter مجرد مصدر بيانات يستخدم لتوضيح أنماط التسلسل الشائعة.
لإجراء تسلسل لمثيل فئة عنوان بسيط، استخدم الكود الزائف التالي لإكمال المهمة:
SimpleSerializer
mySerializer = new SimpleSerializer(typeof(Address));
SimpleDataWriterwriter = new SimpleDataWriter(
)
;
يوصى بتجربة نموذج التعليمات البرمجية بنفسك، وخاصة مكتبة SimpleSerialization. لقد أضفت تعليقات على بعض الأجزاء المثيرة للاهتمام في SimpleSerializer، وآمل أن يساعد ذلك. بالطبع، إذا كنت بحاجة إلى تسلسل صارم في كود الإنتاج، فيجب عليك حقًا الاعتماد على التقنيات المتوفرة في .NET Framework (مثل XmlSerializer). ولكن إذا وجدت أنك بحاجة إلى العمل مع أنواع عشوائية في وقت التشغيل والتعامل معها بكفاءة، آمل أن تعتمد مكتبة SimpleSerialization الخاصة بي كحل لك.