في هذه المقالة ، سأشارك تجربة تسلسل النوع الثنائي بين التجميعات ، دون الرجوع إلى بعضها البعض. كما اتضح ، هناك حالات حقيقية و "شرعية" عندما تحتاج إلى إلغاء تسلسل البيانات دون أن يكون هناك رابط للتجميع حيث يتم الإعلان عنها. في المقالة سوف أتحدث عن السيناريو الذي كان مطلوبًا فيه ، سأصف طريقة الحل ، وسأتحدث أيضًا عن الأخطاء المتوسطة التي تم ارتكابها أثناء البحث
مقدمة بيان المشكلة
نتعاون مع شركة كبيرة تعمل في مجال الجيولوجيا. تاريخيا ، كتبت الشركة برمجيات مختلفة للغاية للعمل مع البيانات القادمة من أنواع مختلفة من المعدات + تحليل البيانات + التنبؤ. للأسف ، كل هذا البرنامج بعيد كل البعد عن كونه "ودودًا" مع بعضهم البعض ، وغالبًا ما يكون غير ودي على الإطلاق. من أجل دمج المعلومات بطريقة أو بأخرى ، يتم الآن إنشاء بوابة ويب ، حيث تقوم برامج مختلفة بتحميل بياناتها في شكل XML. وتحاول البوابة إنشاء عرض زائد ناقص. فارق بسيط مهم: نظرًا لأن مطوري البوابة ليسوا أقوياء في مجالات موضوع كل تطبيق ، قدم كل فريق محولًا نموذجيًا / محول بيانات من xml إلى هياكل بيانات البوابة.
أعمل في فريق يعمل على تطوير أحد التطبيقات وكتبنا بسهولة آلية تصدير لجزء البيانات الخاص بنا. ولكن هنا ، قرر محلل الأعمال أن البوابة المركزية بحاجة إلى أحد التقارير التي كان برنامجنا يقوم ببنائها. هنا ظهرت المشكلة الأولى: يتم إنشاء التقرير من جديد في كل مرة ولا يتم حفظ النتائج في أي مكان.
"لذا احفظه!" ربما يعتقد القارئ. فكرت بذلك أيضًا ، ولكن شعرت بخيبة أمل شديدة من مطلب إنشاء التقرير بالفعل للبيانات التي تم تنزيلها. لا يوجد شيء للقيام به - تحتاج إلى نقل المنطق.
المرحلة 0. إعادة بيع ديون. لا شيء يبشر بالمتاعب
تقرر فصل منطق بناء التقرير (في الواقع ، هذا عبارة عن ملصق مكون من 4 أعمدة ، ولكن المنطق عبارة عن عربة وعربة كبيرة) في فئة منفصلة ، وتضمين الملف مع هذه الفئة بالإشارة في تجميع المحلل. من خلال هذا نحن:
- تجنب النسخ المباشر
- الحماية من التناقضات في الإصدار
إن فصل المنطق إلى فصل منفصل ليس مهمة صعبة. ولكن بعد ذلك لم يكن كل شيء ورديًا: فقد كانت الخوارزمية تعتمد على كائنات الأعمال ، والتي لم يتناسب نقلها مع مفهومنا. اضطررت إلى إعادة كتابة الأساليب بحيث تقبل الأنواع البسيطة فقط وتعمل عليها. لم يكن الأمر دائمًا بسيطًا وفي الأماكن ، كان يتطلب قرارات ، ظل جمالها محل تساؤل ، ولكن بشكل عام ، تم الحصول على حل موثوق بدون عكازات واضحة.
كان هناك تفصيل واحد ، كما تعلمون ، غالبًا ما يعمل كملاذ مريح للشيطان: لقد ورثنا نهجًا غريبًا من الأجيال السابقة من المطورين ، حيث يتم تخزين بعض البيانات المطلوبة لإنشاء تقرير في قاعدة البيانات على أنها كائنات ثنائية متسلسلة. الأسئلة "لماذا؟" ، "كعك؟" ، إلخ ، للأسف ، ستبقى دون إجابة بسبب نقص المرسل إليهم). وفي مدخلات الحسابات ، بالطبع ، يجب أن نزيلها.
هذه الأنواع ، التي كان من المستحيل التخلص منها ، قمنا أيضًا بتضمينها "بالإشارة" ، خاصة أنها لم تكن معقدة.
المرحلة 1. نزع التسلسل. تذكر اسم النوع الكامل
بعد القيام بالتلاعبات المذكورة أعلاه وإجراء تشغيل اختباري ، تلقيت خطأ وقت التشغيل بشكل غير متوقع
لا يمكن إرسال [A] Namespace.TypeA إلى [B] Namespace.TypeA. النوع أ نشأ من "Assembley.Application، Version = 1.0.0.0، Culture = Neutral، PublicKeyToken = null" في السياق "افتراضي" في الموقع "...". النوع B ينشأ من "Assmbley.Portal ، الإصدار = 1.0.0.0 ، الثقافة = محايد ، PublicKeyToken = فارغ" في السياق "افتراضي" في الموقع "".
أخبرتني أول روابط Google أن الحقيقة هي أن BinaryFormatter لا يكتب البيانات فحسب ، بل يكتب أيضًا المعلومات إلى دفق الإخراج ، وهو أمر منطقي. ومع الأخذ في الاعتبار أن الاسم الكامل للنوع يحتوي على التجميع الذي تم التصريح به ، فإن صورة ما حاولت إلغاء نوع منه هو مختلف تمامًا عن وجهة نظر .Net
بعد أن خدشت رأسي ، اتخذت ، كما يحدث ، قرارًا واضحًا ، ولكن للأسف ، شرير ، لاستبدال نوع TypeA معين أثناء إزالة التسلسل
الديناميكي . كل شيء يعمل. تقاربت نتائج التقرير من أعلى إلى أسفل ، مرت الاختبارات على خادم البناء. مع شعور بالإنجاز ، نرسل المهمة إلى المختبرين.
المرحلة 2. الرئيسية. التسلسل بين التجمعات
جاء الحساب بسرعة على شكل أخطاء مسجلة من قبل المختبرين ، والتي ذكرت أن المحلل اللغوي على جانب المدخل سقط باستثناء أنه لا يمكن تحميل التجميع Assembley.Application (تجميع من تطبيقنا). الفكر الأول - لم أنظف المراجع. ولكن - لا ، كل شيء على ما يرام ، لا أحد يشير. أحاول تشغيله مرة أخرى في وضع الحماية - كل شيء يعمل. أبدأ في الشك في وجود خطأ في البناء ، ولكن هنا يتبادر إلى ذهني فكرة لا ترضيني: أقوم بتغيير مسار الإخراج للمحلل إلى مجلد منفصل ، وليس إلى دليل الحاوية المشتركة للتطبيق. وفويلا - أحصل على الاستثناء الموصوف. يؤكد تحليل Stectrace التخمينات المبهمة - إزالة التسلسل آخذة في الانخفاض.
كان الوعي سريعًا ومؤلمًا: استبدال نوع معين بديناميكي لم يغير أي شيء ، لا يزال BinaryFormatter ينشئ نوعًا من تجميع خارجي ، فقط عندما يكون التجميع مع النوع قريبًا ، ووقت التشغيل الذي تم تحميله بشكل طبيعي ، وعندما اختفى التجميع - نحصل على خطأ.
كان هناك سبب للحزن. لكن غوغلينغ أعطى الأمل في شكل
SerializationBinder Class . كما اتضح ، فإنه يسمح لك بتحديد النوع الذي يتم فيه إلغاء تسلسل بياناتنا. للقيام بذلك ، قم بإنشاء وريث وحدد الطريقة التالية فيه.
public abstract Type BindToType(String assemblyName, String typeName);
حيث يمكنك إرجاع أي نوع لظروف معينة.
تحتوي فئة BinaryFormatter على خاصية
Binder حيث يمكنك إدخال التطبيق الخاص بك.
يبدو أنه لا توجد مشكلة. ولكن مرة أخرى ، تبقى التفاصيل (انظر أعلاه).
أولاً ، يجب عليك معالجة الطلبات
لجميع الأنواع (والمعيارية أيضًا).
تم العثور على خيار تنفيذ مثير للاهتمام على الإنترنت هنا ، لكنهم يحاولون استخدام الرابط الافتراضي من BinaryFormatter ، في شكل إنشاء
var defaultBinder = new BinaryFormatter().Binder
ولكن في الواقع ، تكون خاصية Binder فارغة بشكل افتراضي. أظهر تحليل لشفرة المصدر أنه داخل BinaryFormatter ، سواء تم فحص Binder ، إذا كان الأمر كذلك ، يتم استدعاء أساليبها ، إذا لم يتم ذلك ، يتم استخدام المنطق الداخلي ، والذي يتلخص في النهاية في
var assembly = Assembly.Load(assemblyName); return FormatterServices.GetTypeFromAssembly(assembly, typeName);
دون مزيد من اللغط ، كررت نفس المنطق في نفسي.
إليك ما حدث في التنفيذ الأول
public class MyBinder : SerializationBinder { public override Type BindToType(string assemblyName, string typeName) { if (assemblyName.Contains("<ObligatoryPartOfNamespace>") ) { var bindToType = Type.GetType(typeName); return bindToType; } else { var bindToType = LoadTypeFromAssembly(assemblyName, typeName); return bindToType; } } private Type LoadTypeFromAssembly(string assemblyName, string typeName) { if (string.IsNullOrEmpty(assemblyName) || string.IsNullOrEmpty(typeName)) return null; var assembly = Assembly.Load(assemblyName); return FormatterServices.GetTypeFromAssembly(assembly, typeName); } }
أي يتم فحص ما إذا كانت مساحة الاسم تنتمي إلى المشروع - نعيد النوع من المجال الحالي ، إذا كان نوع النظام - نقوم بالتحميل من التجميع المقابل
يبدو منطقيا. نبدأ الاختبار: يأتي نوعنا - نستبدل ، يتم إنشاؤه. الصيحة! تأتي السلسلة - نذهب على طول الفرع مع التحميل من التجميع. يعمل! فتح الشمبانيا الافتراضية ...
ولكن هنا ... يأتي القاموس مع عناصر من أنواع المستخدمين: نظرًا لأن هذا هو نوع النظام ، فمن الواضح ... أننا نحاول تحميله من التجميع ، ولكن نظرًا لأن العناصر الموجودة فيه هي أنواعنا ، علاوة على ذلك ، مع التأهيل الكامل (التجميع ، الإصدار ، المفتاح ) ، ثم نسقط مرة أخرى. (يجب أن تكون هناك ابتسامة حزينة).
من الواضح أنك تحتاج إلى تغيير اسم الإدخال من النوع ، واستبدال الروابط بالتجميع المطلوب. كنت أتمنى حقًا أنه بالنسبة لاسم النوع ، هناك نظير من فئة
AssemblyName ، لكنني لم أجد أي شيء مماثل. إن كتابة محلل شامل مع الاستبدال ليست مهمة سهلة. بعد سلسلة من التجارب ، توصلت إلى الحل التالي: في المنشئ الثابت ، أطرح الأنواع لاستبدالها ، ثم أبحث عن أسمائهم في السطر مع اسم النوع الذي تم إنشاؤه ، وعندما أجده ، أستبدل اسم التجميع
كما ترى ، لقد بدأت من حقيقة أن PublicKeyToken هو الأخير في وصف النوع. ربما هذا ليس موثوقًا بنسبة 100 ٪ ، ولكن في اختباراتي لم أجد حالات حيث لم يكن الأمر كذلك.
وبالتالي ، خط النموذج
"System.Collections.Generic.Dictionary`2 [[SomeNamespace.CustomType، Assembley.Application، Version = 1.0.0.0، Culture = Neutral، PublicKeyToken = null]، [System.Byte []، mscorlib، الإصدار = 4.0.0.0، الثقافة = محايد ، PublicKeyToken = b77a5c561934e089]] »
يتحول إلى
"System.Collections.Generic.Dictionary`2 [[SomeNamespace.CustomType، Assembley.Portal، Version = 1.0.0.0، Culture = Neutral، PublicKeyToken = null]، [System.Byte []، mscorlib، الإصدار = 4.0.0.0، الثقافة = محايد ، PublicKeyToken = b77a5c561934e089]] »
الآن عمل كل شيء أخيرًا "مثل الساعة". كانت هناك خفايا فنية ثانوية: إذا كنت تتذكر ، فقد تم تضمين الملفات التي قمنا بتضمينها في الرابط من التطبيق الرئيسي. ولكن في التطبيق الرئيسي ليست هناك حاجة إلى كل هذه الرقصات. لذلك ، آلية تجميع مشروطة للنموذج
BinaryFormatter binForm = new BinaryFormatter(); #if EXTERNAL_LIB binForm.Binder = new MyBinder(); #endif
وفقًا لذلك ، في تجميع المدخل نحدد الماكرو EXTERNAL_LIB ، ولكن في التطبيق الرئيسي - لا
"انحراف غير غنائي"
في الواقع ، في عملية الترميز ، من أجل التحقق من الحل بسرعة ، قمت بحساب خاطئ واحد ، والذي ربما يكلفني عددًا معينًا من الخلايا العصبية: بالنسبة للمبتدئين ، قمت فقط بترميز نوع استبدال Dicitionary. ونتيجة لذلك ، بعد إلغاء التسلسل ، تبين أنه قاموس فارغ ، والذي "انهار" أيضًا عند محاولة إجراء بعض العمليات معه. لقد بدأت بالفعل في التفكير في أنه لا يمكنك خداع BinaryFormatter ، وبدأت تجارب يائسة في محاولة لكتابة وريث القاموس. لحسن الحظ ، توقفت في الوقت المحدد تقريبًا وعدت لكتابة آلية استبدال عالمية ، وأدركت أنها لإنشاء قاموس لا يكفي لإعادة تعريف نوعه: ما زلت بحاجة إلى رعاية أنواع KeyValuePair <TKey ، TValue> ، المقارنة ، والمطلوبة أيضًا من بيندر
هذه هي مغامرات التسلسل الثنائي. سأكون ممتنا لردود الفعل.