24-Bob: Runtime Serialization

Serializatsiya va deserializatsiya asoslari, ISerializable interfeysi, formatterlar, surrogat turlar, oqim kontekstlari va SerializationBinder

Kirish

Serializatsiya — bu obyektni yoki bir-biriga bog'langan obyektlar grafini baytlar oqimiga aylantirish jarayonidir. Deserializatsiya — bu baytlar oqimini qaytadan obyektlar grafiga aylantirish jarayonidir. Obyektlarni baytlar oqimiga va baytlar oqimidan aylantirishning qobiliyati nihoyatda foydali mexanizmdir. Quyida ba'zi misollar keltirilgan:

  • Ilovaning holati (obyekt grafi) disk fayliga yoki ma'lumotlar bazasiga osongina saqlanishi va keyingi safar ilova ishga tushirilganda qayta tiklanishi mumkin. ASP.NET sessiya holatini serializatsiya va deserializatsiya orqali saqlaydi va qayta tiklaydi.
  • Obyektlar to'plami tizimning buferga nusxalash (clipboard) ga osongina nusxalanishi va keyin shu yoki boshqa ilovaga joylashtirilishi mumkin. Windows Forms va WPF buni ishlatadi.
  • Obyektlar to'plami klonlanishi va foydalanuvchi "asosiy" to'plam bilan ishlaganda "zaxira" sifatida saqlanishi mumkin.
  • Obyektlar to'plami tarmoq orqali boshqa mashinada ishlaydigan jarayonga osongina yuborilishi mumkin. Microsoft .NET Framework ning remoting arxitekturasi qiymat bo'yicha marshal qilinadigan obyektlarni serializatsiya va deserializatsiya qiladi. Bu AppDomain chegaralari bo'ylab obyektlarni yuborish uchun ham ishlatiladi (22-bobda, "CLR Hosting va AppDomainlar"da muhokama qilinganidek).

Bundan tashqari, yuqoridagi misollardan so'ng, baytlar oqimidagi serializatsiya qilingan obyektlarni xotirada saqlaganingizdan keyin, ma'lumotlarni shifrlash va siqish kabi yanada foydali usullar bilan qayta ishlash juda oson.

Serializatsiya shu qadar foydali bo'lganidanki, ko'plab dasturchilar bu turdagi amallarni bajarish uchun kod yozishga son-sanoqsiz soatlar sarflashgani ajablanarli emas. Tarixan bu kodni yozish qiyin bo'lgan va juda ko'p vaqt talab qilgan hamda xatolarga moyil bo'lgan. Dasturchilar hal qilishlari kerak bo'lgan qiyin muammolardan ba'zilari aloqa protokollari, klient/server ma'lumot turlari nomuvofiqliklari (masalan, little-endian/big-endian masalalari), xatolarni qayta ishlash, boshqa obyektlarga murojaat qiladigan obyektlar, kiruvchi va chiquvchi parametrlar, massivlar va strukturalardir — va bu ro'yxat davom etadi.

Siz .NET Framework ning serializatsiya va deserializatsiya uchun ajoyib qo'llab-quvvatiga ega ekanligini bilishdan xursand bo'lasiz. Bu shuni anglatadiki, yuqorida aytib o'tilgan barcha qiyin muammolar endi .NET Framework tomonidan to'liq va shaffof tarzda hal qilinadi. Dasturchi sifatida siz serializatsiyadan oldin va deserializatsiyadan keyin obyektlaringiz bilan ishlashingiz mumkin va .NET Framework o'rtadagi ishlarni o'zi bajaradi.

Ushbu bobda men .NET Framework ning serializatsiya va deserializatsiya xizmatlarini qanday ta'minlashini tushuntiraman. Deyarli barcha ma'lumot turlari uchun bu xizmatlarning standart harakati yetarli bo'ladi, ya'ni o'zingizning turlaringizni serializatsiya qilinadigan qilish uchun deyarli hech narsa qilishingiz shart emas. Biroq, serializatsiya xizmatining standart harakati yetarli bo'lmaydigan turlarning kichik ozchiligi mavjud. Yaxshiyamki, serializatsiya xizmatlari juda kengaytiriladigan va men bu kengaytirish mexanizmlariga qanday kirishni ham tushuntiraman. Masalan, men obyektning 1-versiyasini disk fayliga serializatsiya qilish va bir yildan keyin 2-versiya obyektiga deserializatsiya qilishni ko'rsataman.

Eslatma

Ushbu bob umumiy til ish vaqti muhiti (CLR) dagi runtime serializatsiya texnologiyasiga qaratilgan bo'lib, u CLR ma'lumot turlarini chuqur tushunadi va barcha public, protected, internal, hatto private maydonlarni yuqori samaradorlik uchun siqilgan ikkilik (binary) oqimga serializatsiya qila oladi. Agar CLR ma'lumot turlarini XML oqimga serializatsiya qilmoqchi bo'lsangiz, System.Runtime.Serialization.NetDataContractSerializer klassini ko'ring. .NET Framework CLR va non-CLR ma'lumot turlari o'rtasida o'zaro ishlash uchun mo'ljallangan boshqa serializatsiya texnologiyalarini ham taqdim etadi. Bu boshqa texnologiyalar System.Xml.Serialization.XmlSerializer klassi va System.Runtime.Serialization.DataContractSerializer klassidan foydalanadi.

Serializatsiya/Deserializatsiya: Tezkor Boshlash

Keling, ba'zi kodlarni ko'rib chiqishdan boshlaylik.

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

internal static class QuickStart {
    public static void Main() {
        // Oqimga serializatsiya qilish uchun obyektlar grafini yarating
        var objectGraph = new List<String> { "Jeff", "Kristin", "Aidan", "Grant" };
        Stream stream = SerializeToMemory(objectGraph);

        // Bu demo uchun hamma narsani qayta o'rnatish
        stream.Position = 0;
        objectGraph = null;

        // Obyektlarni deserializatsiya qilish va ishlashini isbotlash
        objectGraph = (List<String>) DeserializeFromMemory(stream);
        foreach (var s in objectGraph) Console.WriteLine(s);
    }

    private static MemoryStream SerializeToMemory(Object objectGraph) {
        // Serializatsiya qilingan obyektlarni saqlash uchun oqim yarating
        MemoryStream stream = new MemoryStream();

        // Barcha og'ir ishni bajaradigan serializatsiya formatterini yarating
        BinaryFormatter formatter = new BinaryFormatter();

        // Formatterga obyektlarni oqimga serializatsiya qilishni ayting
        formatter.Serialize(stream, objectGraph);

        // Serializatsiya qilingan obyektlar oqimini chaqiruvchiga qaytaring
        return stream;
    }

    private static Object DeserializeFromMemory(Stream stream) {
        // Barcha og'ir ishni bajaradigan serializatsiya formatterini yarating
        BinaryFormatter formatter = new BinaryFormatter();

        // Formatterga obyektlarni oqimdan deserializatsiya qilishni ayting
        return formatter.Deserialize(stream);
    }
}

Qarang, bu qanchalik sodda! SerializeToMemory metodi System.IO.MemoryStream obyektini yaratadi. Bu obyekt serializatsiya qilingan baytlar blokining qaerga joylashtirilishini aniqlaydi. Keyin metod BinaryFormatter obyektini yaratadi (uni System.Runtime.Serialization.Formatters.Binary nomlar fazosida topish mumkin). Formatter — bu System.Runtime.Serialization.IFormatter interfeysini amalga oshiruvchi tur bo'lib, u obyektlar grafini qanday serializatsiya va deserializatsiya qilishni biladi. Framework Class Library (FCL) ikkita formatter bilan birga keladi: BinaryFormatter (ushbu kod misolida ishlatilgan) va SoapFormatter (System.Runtime.Serialization.Formatters.Soap nomlar fazosida joylashgan va System.Runtime.Serialization.Formatters.Soap.dll assembliyasida amalga oshirilgan).

Eslatma

.NET Framework ning 3.5 versiyasidan boshlab SoapFormatter klassi eskirgan va ishlab chiqarish kodida ishlatilmasligi kerak. Biroq, u o'qilishi mumkin bo'lgan XML matn ishlab chiqargani uchun serializatsiya kodini nosozliklarni tuzatishda foydali bo'lishi mumkin. Ishlab chiqarishda XML serializatsiyasidan foydalanish uchun XmlSerializer va DataContractSerializer klasslarini ko'ring.

Obyektlar grafini serializatsiya qilish uchun formatterning Serialize metodini chaqiring va unga ikkita narsa bering: oqim obyektiga havola va serializatsiya qilmoqchi bo'lgan obyektlar grafiga havola. Oqim obyekti serializatsiya qilingan baytlar qaerga joylashtirilishini aniqlaydi va System.IO.Stream abstrakt bazaviy sinfdan hosil bo'lgan istalgan turdagi obyekt bo'lishi mumkin. Bu shuni anglatadiki, siz obyekt grafini MemoryStream, FileStream, NetworkStream va boshqalarga serializatsiya qilishingiz mumkin.

Serialize ga beriladigan ikkinchi parametr obyektga havola bo'lib, u istalgan narsa bo'lishi mumkin: Int32, String, DateTime, Exception, List<String>, Dictionary<Int32, DateTime> va hokazo. objectGraph parametri bilan ko'rsatilgan obyekt boshqa obyektlarga murojaat qilishi mumkin. Masalan, objectGraph obyektlar to'plamiga murojaat qilishi mumkin. Bu obyektlar ham boshqa obyektlarga murojaat qilishi mumkin. Formatterning Serialize metodi chaqirilganda, grafdagi barcha obyektlar oqimga serializatsiya qilinadi.

Formatterlar juda aqlli algoritmlarga ega. Ular grafdagi har bir obyektni oqimga faqat bir marta serializatsiya qilishni biladi. Ya'ni, agar grafdagi ikkita obyekt bir-biriga murojaat qilsa, formatter buni aniqlaydi, har bir obyektni faqat bir marta serializatsiya qiladi va cheksiz tsiklga tushishdan qochadi.

Mening SerializeToMemory metodimda, formatterning Serialize metodi qaytganda, MemoryStream chaqiruvchiga qaytariladi. Ilova bu yassi baytlar massivining mazmunidan xohlagan tarzda foydalanishi mumkin. Masalan, uni faylga saqlashi, buferga nusxalashi, tarmoq orqali yuborishi va hokazo mumkin.

DeserializeFromMemory metodi oqimni qaytadan obyektlar grafiga deserializatsiya qiladi. Bu metod serializatsiyadan ham oddiyroqdir. Bu kodda BinaryFormatter yaratiladi va keyin uning Deserialize metodi chaqiriladi. Bu metod oqimni parametr sifatida oladi va deserializatsiya qilingan obyektlar grafidagi ildiz obyektga havolani qaytaradi.

Ichki tarzda, formatterning Deserialize metodi oqimning tarkibini tekshiradi, oqimdagi barcha obyektlarning nusxalarini yaratadi va bu obyektlardagi barcha maydonlarni boshlaydi, shunda ular obyektlar grafi serializatsiya qilinganidagi kabi bir xil qiymatlarga ega bo'ladi. Odatda, siz Deserialize metodidan qaytarilgan obyekt havolasini ilovangiz kutayotgan turga kast qilasiz.

Eslatma: Chuqur nusxalash uchun foydali usul

Quyida obyektning chuqur nusxasini (deep copy) yoki klonini yaratish uchun serializatsiyadan foydalanadigan qiziqarli va foydali metod keltirilgan.

private static Object DeepClone(Object original) {
    // Vaqtinchalik xotira oqimini yaratish
    using (MemoryStream stream = new MemoryStream()) {
        // Barcha og'ir ishni bajaradigan serializatsiya formatterini yarating
        BinaryFormatter formatter = new BinaryFormatter();

        // Bu satr bobning "Oqim Kontekstlari" bo'limida tushuntiriladi
        formatter.Context = new StreamingContext(StreamingContextStates.Clone);

        // Obyekt grafini xotira oqimiga serializatsiya qilish
        formatter.Serialize(stream, original);

        // Deserializatsiya qilishdan oldin xotira oqimining boshiga qaytish
        stream.Position = 0;

        // Grafni yangi obyektlar to'plamiga deserializatsiya qilish va
        // grafning ildiziga havolani (chuqur nusxani) chaqiruvchiga qaytarish
        return formatter.Deserialize(stream);
    }
}

Bu nuqtada, men muhokamamizga bir nechta eslatma qo'shmoqchiman. Birinchidan, kodingiz serializatsiya va deserializatsiya uchun bir xil formatterdan foydalanishini ta'minlash sizning o'zingizga bog'liq. Masalan, SoapFormatter yordamida obyektlar grafini serializatsiya qiladigan va keyin BinaryFormatter yordamida grafni deserializatsiya qiladigan kod yozmang. Agar Deserialize oqim tarkibini tushuna olmasa, System.Runtime.Serialization.SerializationException istisno chiqariladi.

Ikkinchi aytmoqchi bo'lgan narsam shuki, bir nechta obyektlar grafini bitta oqimga serializatsiya qilish mumkin va bu juda foydalidir. Masalan, quyidagi ikkita sinf ta'rifi bor deyaylik:

[Serializable] internal sealed class Customer { /* ... */ }
[Serializable] internal sealed class Order    { /* ... */ }

Va keyin ilovamizning asosiy klassida quyidagi statik maydonlarni aniqlaylik.

private static List<Customer> s_customers       = new List<Customer>();
private static List<Order>    s_pendingOrders    = new List<Order>();
private static List<Order>    s_processedOrders  = new List<Order>();

Endi ilovamizning holatini bitta oqimga serializatsiya qilishimiz mumkin:

private static void SaveApplicationState(Stream stream) {
    // Barcha og'ir ishni bajaradigan serializatsiya formatterini yarating
    BinaryFormatter formatter = new BinaryFormatter();

    // Ilovamizning butun holatini serializatsiya qilish
    formatter.Serialize(stream, s_customers);
    formatter.Serialize(stream, s_pendingOrders);
    formatter.Serialize(stream, s_processedOrders);
}

Ilovamizning holatini qayta tiklash uchun deserializatsiya qiladigan metod quyidagicha ko'rinadi:

private static void RestoreApplicationState(Stream stream) {
    // Barcha og'ir ishni bajaradigan serializatsiya formatterini yarating
    BinaryFormatter formatter = new BinaryFormatter();

    // Ilovamizning butun holatini deserializatsiya qilish (serializatsiya tartibida)
    s_customers       = (List<Customer>) formatter.Deserialize(stream);
    s_pendingOrders   = (List<Order>)    formatter.Deserialize(stream);
    s_processedOrders = (List<Order>)    formatter.Deserialize(stream);
}

Uchinchi va oxirgi aytmoqchi bo'lgan narsa assembliyalar bilan bog'liq. Obyektni serializatsiya qilayotganda, turning to'liq nomi va turni aniqlagan assembliyaning nomi oqimga yoziladi. Odatiy bo'lib, BinaryFormatter assembliyaning to'liq identifikatorini chiqaradi, bu assembliyaning fayl nomini (kengaytmasiz), versiya raqamini, madaniyatini va ochiq kalit ma'lumotini o'z ichiga oladi. Deserializatsiya qilayotganda, formatter avval assembliyaning identifikatorini oladi va System.Reflection.Assembly ning Load metodini chaqirib assembliyaning bajarilayotgan AppDomain ga yuklanganligini ta'minlaydi (23-bob, "Assembliyalarni yuklash va Refleksiya"da muhokama qilingan).

Assembliya yuklangandan keyin, formatter assembliyada deserializatsiya qilinayotgan obyektning turiga mos turni qidiradi. Agar assembliyada mos tur bo'lmasa, istisno chiqariladi va boshqa obyektlar deserializatsiya qilinmaydi. Mos tur topilsa, turning nusxasi yaratiladi va uning maydonlari oqimdan o'qilgan qiymatlar bilan boshlang'ichlanadi. Agar turning maydonlari oqimdan o'qilgan maydonlar nomlari bilan aniq mos kelmasa, SerializationException chiqariladi va boshqa obyektlar deserializatsiya qilinmaydi. Bobning keyingi qismlarida men bu xatti-harakatning bir qismini bekor qilish imkonini beradigan murakkab mexanizmlarni muhokama qilaman.

Muhim

Ba'zi kengaytiriladigan ilovalar assembliyani yuklash uchun Assembly.LoadFrom dan foydalanadi va keyin yuklangan assembliyada aniqlangan turlardan obyektlar yaratadi. Bu obyektlar muammosiz oqimga serializatsiya qilinishi mumkin. Biroq, bu oqimni deserializatsiya qilayotganda, formatter LoadFrom metodini chaqirish o'rniga Assembly ning Load metodini chaqirib assembliyani yuklashga harakat qiladi. Ko'p hollarda CLR assembliya faylini topa olmaydi va SerializationException chiqarilishiga sabab bo'ladi.

Agar ilovangiz Assembly.LoadFrom orqali yuklangan assembliyada aniqlangan turlarning obyektlarini serializatsiya qilsa, System.ResolveEventHandler delegatiga mos imzoga ega metod yaratib, uni System.AppDomain ning AssemblyResolve hodisasiga ro'yxatdan o'tkazishni tavsiya qilaman. Buni formatterning Deserialize metodini chaqirishdan oldin qiling. (Deserialize qaytgandan keyin hodisadan ro'yxatdan o'chirish kerak.)

Bu bo'lim obyektlar grafini qanday serializatsiya va deserializatsiya qilish asoslarini qamrab oldi. Qolgan bo'limlarda biz o'zimizning serializatsiya qilinadigan turlarimizni aniqlash uchun nima qilishimiz kerakligini ko'rib chiqamiz va serializatsiya va deserializatsiya ustidan ko'proq nazoratga ega bo'lish imkonini beruvchi turli mexanizmlarni ham ko'rib chiqamiz.

Turni Serializatsiya Qilinadigan Qilish

Tur loyihalashda dasturchi ushbu turning nusxalarini serializatsiya qilishga ruxsat berish yoki bermaslik to'g'risida ongli qaror qabul qilishi kerak. Odatiy holda, turlar serializatsiya qilinadigan emas. Masalan, quyidagi kod kutilganidek ishlamaydi:

internal struct Point { public Int32 x, y; }

private static void OptInSerialization() {
    Point pt = new Point { x = 1, y = 2 };
    using (var stream = new MemoryStream()) {
        new BinaryFormatter().Serialize(stream, pt); // SerializationException chiqaradi
    }
}

Agar siz ushbu kodni kompilyatsiya qilsangiz va ishga tushirsangiz, formatterning Serialize metodining System.Runtime.Serialization.SerializationException istisno chiqarishini ko'rasiz. Muammo shundaki, Point turining dasturchisi Point obyektlari serializatsiya qilinishi mumkinligini aniq ko'rsatmagan. Bu muammoni hal qilish uchun dasturchi ushbu turga System.SerializableAttribute maxsus atributini quyidagicha qo'llashi kerak. (E'tibor bering, bu atribut System nomlar fazosida aniqlangan, System.Runtime.Serialization nomlar fazosida emas.)

[Serializable]
internal struct Point { public Int32 x, y; }

Endi ilovani qayta qursangiz va ishga tushirsangiz, u kutilganidek ishlaydi va Point obyektlari oqimga serializatsiya qilinadi. Obyektlar grafini serializatsiya qilayotganda, formatter grafdagi har bir obyektning turining serializatsiya qilinadigan ekanligini tekshiradi. Agar grafdagi biron-bir obyekt serializatsiya qilinadigan bo'lmasa, formatterning Serialize metodi SerializationException istisnosini chiqaradi.

Eslatma

Obyektlar grafini serializatsiya qilayotganda, ba'zi turlar serializatsiya qilinadigan bo'lishi mumkin, ba'zilari esa bo'lmasligi mumkin. Samaradorlik sabablari tufayli, formatterlar grafni serializatsiya qilishdan oldin grafdagi barcha obyektlar serializatsiya qilinadigan ekanligini tekshirmaydi. Shunday qilib, SerializationException chiqarilishidan oldin ba'zi obyektlar oqimga serializatsiya qilingan bo'lishi butunlay mumkin. Agar bu sodir bo'lsa, oqim buzilgan (corrupt) ma'lumotlarni o'z ichiga oladi. Agar siz ba'zi obyektlar serializatsiya qilinadigan bo'lmasligi mumkin bo'lgan obyekt grafini serializatsiya qilayotgan deb o'ylasangiz, ilova kodingiz bu holatdan oqilona tiklana olishi kerak. Variantlardan biri — obyektlarni avval MemoryStream ga serializatsiya qilish. Keyin barcha obyektlar muvaffaqiyatli serializatsiya qilinsa, MemoryStream dagi baytlarni baytlarni haqiqatan ham yozmoqchi bo'lgan oqimga (masalan, fayl, tarmoq) nusxalashingiz mumkin.

SerializableAttribute maxsus atributi reference turlarga (class), qiymat turlarga (struct), sanab o'tiladigan turlarga (enum) va delegat turlarga (delegate) qo'llanishi mumkin. (E'tibor bering, sanab o'tiladigan va delegat turlar doimo serializatsiya qilinadigan bo'ladi, shuning uchun bu turlarga SerializableAttribute atributini aniq qo'llash shart emas.) Bundan tashqari, SerializableAttribute atributi hosilaviy turlar tomonidan meros qilib olinmaydi. Shunday qilib, quyidagi ikkita tur ta'rifi berilgan holda, Person obyekti serializatsiya qilinishi mumkin, lekin Employee obyekti serializatsiya qilinmaydi.

[Serializable]
internal class Person { ... }

internal class Employee : Person { ... }

Buni tuzatish uchun Employee turiga ham SerializableAttribute atributini qo'llash kifoya:

[Serializable]
internal class Person { ... }

[Serializable]
internal class Employee : Person { ... }

E'tibor bering, bu muammoni hal qilish oson edi. Biroq, aksinchasi — SerializableAttribute atributi qo'llanilmagan bazaviy turdan hosil bo'lgan turni aniqlash — hal qilish oson emas. Lekin bu qasddan shunday qilingan; agar bazaviy tur o'z turining nusxalarini serializatsiya qilishga ruxsat bermasa, uning maydonlari serializatsiya qilinmaydi, chunki bazaviy obyekt hosilaviy obyektning samarali bir qismidir. Bu System.Object ga SerializableAttribute atributi qo'llanilganining sababidir.

Eslatma

Umuman olganda, siz aniqlagan ko'pchilik turlar serializatsiya qilinadigan bo'lishi tavsiya etiladi. Axir, bu turlaringiz foydalanuvchilariga ko'p moslashuvchanlik beradi. Biroq, shuni yodda tutingki, serializatsiya maydonlar public, protected, internal yoki private deb e'lon qilinganligidan qat'i nazar obyektning barcha maydonlarini o'qiydi. Agar tur sezgir yoki maxfiy ma'lumotlarni (masalan, parollarni) o'z ichiga olsa yoki ma'lumot uzatilganda ma'nosiz bo'lsa yoki qiymatini yo'qotsa, turni serializatsiya qilinadigan qilishni xohlamasligingiz mumkin.

Agar siz serializatsiya uchun mo'ljallangan bo'lmagan turdan foydalanayotgan bo'lsangiz va turning manba kodiga ega bo'lmasangiz va turga serializatsiya qo'llab-quvvatlashni qo'sha olmasangiz, hamma narsa yo'qolgan emas. Ushbu bobning keyingi qismidagi "Obyektni deserializatsiya qilayotganda assembliya va/yoki turni almashtirish" bo'limida men har qanday serializatsiya qilinmaydigan turni qanday serializatsiya qilinadigan qilish mumkinligini tushuntiraman.

Serializatsiya va Deserializatsiyani Boshqarish

Turga SerializableAttribute maxsus atributini qo'llaganingizda, barcha nusxa maydonlari (public, private, protected va hokazo) serializatsiya qilinadi. Biroq, turda serializatsiya qilinmasligi kerak bo'lgan ba'zi nusxa maydonlari bo'lishi mumkin. Umuman olganda, turning ba'zi nusxa maydonlarini serializatsiya qilishni xohlamasligingizning ikkita sababi bor:

  • Maydon deserializatsiya qilinganida amal qilmaydigan ma'lumotni o'z ichiga oladi. Masalan, Windows yadro obyektiga (fayl, jarayon, ip (thread), muteqs, hodisa, semafor va hokazo) identifikatorni o'z ichiga olgan obyekt boshqa jarayonga yoki mashinaga deserializatsiya qilinganida ma'nosiz bo'ladi, chunki Windows yadro identifikatorlari jarayonga nisbiy qiymatlardir.
  • Maydon oson hisoblanishi mumkin bo'lgan ma'lumotni o'z ichiga oladi. Bu holda siz qaysi maydonlarni serializatsiya qilishni tanlab, ilovangiz samaradorligini uzatiladigan ma'lumot hajmini kamaytirish orqali yaxshilaysiz.

Quyidagi kod turning qaysi maydonlari serializatsiya qilinmasligi kerakligini ko'rsatish uchun System.NonSerializedAttribute maxsus atributidan foydalanadi. (E'tibor bering, bu atribut ham System nomlar fazosida aniqlangan, System.Runtime.Serialization nomlar fazosida emas.)

[Serializable]
internal class Circle {
    private Double m_radius;

    [NonSerialized]
    private Double m_area;

    public Circle(Double radius) {
        m_radius = radius;
        m_area = Math.PI * m_radius * m_radius;
    }

    ...
}

Yuqoridagi kodda Circle obyektlari serializatsiya qilinishi mumkin. Biroq, formatter faqat obyektning m_radius maydonidagi qiymatlarni serializatsiya qiladi. m_area maydonidagi qiymat serializatsiya qilinmaydi, chunki unga NonSerializedAttribute atributi qo'llanilgan. Bu atribut faqat turning maydonlariga qo'llanishi mumkin va u boshqa turlar tomonidan meros qilib olinganida ham qo'llanilishda davom etadi. Albatta, siz tur ichidagi bir nechta maydonlarga NonSerializedAttribute atributini qo'llashingiz mumkin.

Endi Circle konstruktori quyidagicha ishlaydi deyaylik:

Circle c = new Circle(10);

Ichki tarzda, m_area maydoni taxminan 314.159 ga o'rnatiladi. Bu obyekt serializatsiya qilinganida, faqat m_radius maydoni (10) ning qiymati oqimga yoziladi. Bu biz xohlagan narsa, lekin endi oqim qaytadan Circle obyektiga deserializatsiya qilinganida muammo paydo bo'ladi. Deserializatsiya qilinganida, Circle obyektining m_radius maydoni 10 ga o'rnatiladi, lekin m_area maydoni 0 ga boshlang'ichlanadi, 314.159 emas!

Quyidagi kod bu muammoni hal qilish uchun Circle turini qanday o'zgartirishni ko'rsatadi.

[Serializable]
internal class Circle {
    private Double m_radius;

    [NonSerialized]
    private Double m_area;

    public Circle(Double radius) {
        m_radius = radius;
        m_area = Math.PI * m_radius * m_radius;
    }

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context) {
        m_area = Math.PI * m_radius * m_radius;
    }
}

Men Circle ni o'zgartirdim, shunda endi u System.Runtime.Serialization.OnDeserializedAttribute maxsus atributi bilan belgilangan metodni o'z ichiga oladi. Turning nusxasi deserializatsiya qilinganida, formatter turda ushbu atribut bilan belgilangan metod aniqlangan yoki aniqlanmaganligini tekshiradi va keyin formatter ushbu metodni chaqiradi. Bu metod chaqirilganda, barcha serializatsiya qilinadigan maydonlar to'g'ri o'rnatilgan bo'ladi va ularga murojaat qilib obyektni to'liq deserializatsiya qilish uchun zarur bo'lgan qo'shimcha ishlarni bajarish mumkin bo'ladi.

OnDeserializedAttribute maxsus atributidan tashqari, System.Runtime.Serialization nomlar fazosi OnSerializingAttribute, OnSerializedAttribute va OnDeserializingAttribute maxsus atributlarini ham aniqlaydi, ularni turlaringiz metodlariga qo'llab serializatsiya va deserializatsiya ustidan yanada ko'proq nazoratga ega bo'lishingiz mumkin. Quyida bu atributlarning har birini metod ga qo'llaydigan namuna klass:

[Serializable]
public class MyType {
    Int32 x, y; [NonSerialized] Int32 sum;

    public MyType(Int32 x, Int32 y) {
        this.x = x; this.y = y; sum = x + y;
    }

    [OnDeserializing]
    private void OnDeserializing(StreamingContext context) {
        // Misol: Turning yangi versiyasidagi maydonlar uchun standart qiymatlarni o'rnatish
    }

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context) {
        // Misol: Maydonlardan vaqtinchalik holatni boshlash
        sum = x + y;
    }

    [OnSerializing]
    private void OnSerializing(StreamingContext context) {
        // Misol: Serializatsiya qilishdan oldin har qanday holatni o'zgartirish
    }

    [OnSerialized]
    private void OnSerialized(StreamingContext context) {
        // Misol: Serializatsiya qilishdan keyin har qanday holatni tiklash
    }
}

Bu to'rtta atributning istalganini qo'llaganingizda, siz aniqlagan metod bitta StreamingContext parametrini (ushbu bobning keyingi qismidagi "Oqim Kontekstlari" bo'limida muhokama qilingan) olishi va void qaytarishi kerak. Metodning nomi istalgan narsa bo'lishi mumkin. Shuningdek, metodni private deb e'lon qilishingiz kerak, chunki formatterlar yetarli xavfsizlik bilan ishlagani uchun private metodlarni chaqira oladi.

Eslatma

Obyektlar to'plamini serializatsiya qilganingizda, formatter avval barcha obyektlarning OnSerializing atributi bilan belgilangan metodlarini chaqiradi. Keyin barcha obyektlarning maydonlarini serializatsiya qiladi va nihoyat barcha obyektlarning OnSerialized atributi bilan belgilangan metodlarini chaqiradi. Xuddi shunday, obyektlar to'plamini deserializatsiya qilganingizda, formatter barcha obyektlarning OnDeserializing atributi bilan belgilangan metodlarini chaqiradi, keyin barcha obyektlarning maydonlarini deserializatsiya qiladi va nihoyat barcha obyektlarning OnDeserialized atributi bilan belgilangan metodlarini chaqiradi.

E'tibor bering, deserializatsiya paytida formatter OnDeserialized atributi bilan belgilangan metodni ega turni ko'rganda, bu obyektga havolani ichki ro'yxatga qo'shadi. Barcha obyektlar deserializatsiya qilingandan keyin, formatter bu ro'yxatni teskari tartibda ko'rib chiqadi va har bir obyektning OnDeserialized metodini chaqiradi. Bu muhim, chunki u ichki obyektlarga tashqi obyektlardan oldin deserializatsiyani tugatish imkonini beradi.

Masalan, ichki tarzda xesh jadvalidan foydalanib elementlar to'plamini saqlaydigan to'plam obyektini (masalan, Hashtable yoki Dictionary) tasavvur qiling. To'plam obyekti turida OnDeserialized atributi bilan belgilangan metod bo'lardi. To'plam obyekti avval deserializatsiya qilinishni boshlagan bo'lsa ham (elementlaridan oldin), uning OnDeserialized metodi oxirida chaqiriladi (barcha elementlarning OnDeserialized metodlaridan keyin). Bu elementlarga deserializatsiyani to'liq tugatish, barcha maydonlarini to'g'ri boshlash va yaxshi xesh kod qiymatini hisoblash imkonini beradi. Keyin to'plam obyekti o'zining ichki segmentlarini yaratadi va elementlarni segmentlarga joylashtirish uchun elementlarning xesh kodlaridan foydalanadi.

Agar siz turning nusxasini serializatsiya qilsangiz, turga yangi maydon qo'shsangiz va keyin nusxani deserializatsiya qilishga harakat qilsangiz, formatter deserializatsiya qilinayotgan oqimdagi ma'lumotlarda noto'g'ri a'zolar soni borligini ko'rsatuvchi xabar bilan SerializationException chiqaradi. Bu versiyalash stsenariylarida juda muammoli, chunki turning yangi versiyasiga yangi maydonlar qo'shish odatiy holdir. Yaxshiyamki, System.Runtime.Serialization.OptionalFieldAttribute atributi sizga yordam beradi.

OptionalFieldAttribute atributini turga qo'shgan har bir yangi maydonga qo'llaysiz. Endi formatterlar maydonga bu atribut qo'llanilganini ko'rganda, agar oqimdagi ma'lumotlarda maydon bo'lmasa, SerializationException istisnosini chiqarmaydi.

Formatterlar Turlarning Nusxalarini Qanday Serializatsiya Qiladi

Ushbu bo'limda formatterning obyektning maydonlarini qanday serializatsiya qilishi haqida biroz ko'proq tushuncha beraman. Bu bilim bobning qolgan qismida tushuntirilgan yanada ilg'or serializatsiya va deserializatsiya texnikalarini tushunishga yordam beradi.

Formatterni osonlashtirish uchun FCL System.Runtime.Serialization nomlar fazosida FormatterServices turini taqdim etadi. Bu turda faqat statik metodlar mavjud bo'lib, undan nusxalar yaratib bo'lmaydi. Quyidagi qadamlar SerializableAttribute atributi qo'llanilgan turga ega obyektni formatterning avtomatik ravishda qanday serializatsiya qilishini tasvirlaydi.

Serializatsiya qadamlari

  1. Formatter FormatterServices ning GetSerializableMembers metodini chaqiradi.

    public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context);

    Bu metod refleksiyadan foydalanib turning public va private nusxa maydonlarini oladi (NonSerializedAttribute atributi bilan belgilangan maydonlarni istisno qiladi). Metod har bir serializatsiya qilinadigan nusxa maydoni uchun bittadan MemberInfo obyektlari massivini qaytaradi.

  2. Serializatsiya qilinayotgan obyekt va System.Reflection.MemberInfo obyektlari massivi FormatterServices ning statik GetObjectData metodiga uzatiladi.

    public static Object[] GetObjectData(Object obj, MemberInfo[] members);

    Bu metod har bir element serializatsiya qilinayotgan obyektdagi maydoning qiymatini bildirgan Object massivini qaytaradi. Bu Object massivi va MemberInfo massivi parallel. Ya'ni, Object massividagi 0-element MemberInfo massividagi 0-element tomonidan aniqlangan a'zoning qiymatidir.

  3. Formatter assembliyaning identifikatorini va turning to'liq nomini oqimga yozadi.

  4. Keyin formatter ikkita massiv ustidan takrorlanadi va har bir a'zoning nomi va qiymatini oqimga yozadi.

Deserializatsiya qadamlari

Quyidagi qadamlar SerializableAttribute atributi qo'llanilgan turga ega obyektni formatterning avtomatik ravishda qanday deserializatsiya qilishini tasvirlaydi.

  1. Formatter assembliyaning identifikatorini va to'liq tur nomini oqimdan o'qiydi. Agar assembliya hozirda AppDomain ga yuklanmagan bo'lsa, u yuklanadi (avvalroq tasvirlanganidek). Agar assembliyani yuklab bo'lmasa, SerializationException chiqariladi va obyekt deserializatsiya qilinmaydi. Agar assembliya yuklangan bo'lsa, formatter assembliyaning identifikatorini va turning to'liq nomini FormatterServices ning statik GetTypeFromAssembly metodiga uzatadi.

    public static Type GetTypeFromAssembly(Assembly assem, String name);

    Bu metod deserializatsiya qilinayotgan obyektning turini ko'rsatuvchi System.Type obyektini qaytaradi.

  2. Formatter FormatterServices ning statik GetUninitializedObject metodini chaqiradi.

    public static Object GetUninitializedObject(Type type);

    Bu metod yangi obyekt uchun xotira ajratadi, lekin obyekt uchun konstruktor chaqirmaydi. Biroq, obyektning barcha baytlari 0 yoki null ga boshlang'ichlanadi.

  3. Formatter endi avval qilganidek FormatterServices ning GetSerializableMembers metodini chaqirib MemberInfo massivini yaratadi va boshlaydi. Bu metod serializatsiya qilingan va deserializatsiya qilinishi kerak bo'lgan maydonlar to'plamini qaytaradi.

  4. Formatter oqimdagi ma'lumotlardan Object massivini yaratadi va boshlaydi.

  5. Yangi ajratilgan obyektga havola, MemberInfo massivi va parallel Object maydon qiymatlari massivi FormatterServices ning statik PopulateObjectMembers metodiga uzatiladi.

    public static Object PopulateObjectMembers(
        Object obj, MemberInfo[] members, Object[] data);

    Bu metod massivlar ustidan takrorlanadi va har bir maydonni mos qiymatga boshlaydi. Bu nuqtada, obyekt to'liq deserializatsiya qilingan bo'ladi.

Serializatsiya/Deserializatsiya Qilingan Ma'lumotlarni Boshqarish

Ushbu bobda avvalroq muhokama qilinganidek, serializatsiya va deserializatsiya jarayoni ustidan nazoratga ega bo'lishning eng yaxshi usuli OnSerializing, OnSerialized, OnDeserializing, OnDeserialized, NonSerialized va OptionalField atributlaridan foydalanishdir. Biroq, bu atributlar sizga kerak bo'lgan barcha nazoratni bermaydigan juda kam uchraydigan holatlar mavjud. Bundan tashqari, formatterlar ichki tarzda refleksiyadan foydalanadi va bu sekin, obyektlarni serializatsiya va deserializatsiya qilish uchun ketadigan vaqtni oshiradi. Qaysi ma'lumotlar serializatsiya/deserializatsiya qilinishi ustidan to'liq nazoratga ega bo'lish yoki refleksiya ishlatilishini bartaraf etish uchun, turningiz System.Runtime.Serialization.ISerializable interfeysini amalga oshirishi mumkin, u quyidagicha aniqlangan.

ISerializable Interfeysi

public interface ISerializable {
    void GetObjectData(SerializationInfo info, StreamingContext context);
}

Bu interfeysda faqat bitta metod bor — GetObjectData. Lekin bu interfeysni amalga oshiruvchi ko'pchilik turlar tez orada tasvirlanadigan maxsus konstruktorni ham amalga oshiradi.

Muhim

ISerializable interfeysi bilan bog'liq eng katta muammo shundaki, agar tur uni amalga oshirsa, barcha hosilaviy turlar ham uni amalga oshirishi kerak va hosilaviy turlar bazaviy sinfning GetObjectData metodini va maxsus konstruktorni chaqirishlarini ta'minlashlari kerak. Bundan tashqari, turda bu interfeys amalga oshirilgandan keyin, uni olib tashlab bo'lmaydi, chunki u hosilaviy turlar bilan moslikni yo'qotadi. sealed turlar uchun ISerializable interfeysini amalga oshirish doimo yaxshi. Ushbu bobda avvalroq tasvirlangan maxsus atributlardan foydalanish ISerializable interfeysi bilan bog'liq barcha potensial muammolarning oldini oladi.

Muhim

ISerializable interfeysi va maxsus konstruktor formatterlar tomonidan ishlatilish uchun mo'ljallangan. Biroq, boshqa kod GetObjectData ni chaqirishi mumkin, bu potensial sezgir ma'lumotlarni qaytarishi yoki boshqa kod buzilgan ma'lumotlarni kiritadigan obyektni yaratishi mumkin. Shu sababli, GetObjectData metodiga va maxsus konstruktorga quyidagi atributni qo'llash tavsiya etiladi:

[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]

Formatter obyektlar grafini serializatsiya qilayotganda, u har bir obyektga qaraydi. Agar turida ISerializable interfeysi amalga oshirilgan bo'lsa, formatter barcha maxsus atributlarni e'tiborsiz qoldiradi va o'rniga yangi System.Runtime.Serialization.SerializationInfo obyektini yaratadi. Bu obyektda obyekt uchun serializatsiya qilinishi kerak bo'lgan haqiqiy qiymatlar to'plami mavjud.

SerializationInfo ni yaratayotganda, formatter ikkita parametr uzatadi: Type va System.Runtime.Serialization.IFormatterConverter. Type parametri serializatsiya qilinayotgan obyektni aniqlaydi. Turni noyob aniqlash uchun ikkita ma'lumot zarur: turning satr nomi va turning assembliyasining identifikatori. SerializationInfo obyekti yaratilganida, u turning to'liq nomini va assembliy identifikatorini oladi va saqlaydi. Siz turning to'liq nomini SerializationInfo ning FullTypeName xususiyati orqali, assembliy identifikatorini esa AssemblyName xususiyati orqali olishingiz mumkin.

Eslatma

Garchi SerializationInfo ning FullTypeName va AssemblyName xususiyatlarini o'rnatish mumkin bo'lsa-da, bu tavsiya qilinmaydi. Agar serializatsiya qilinayotgan turni o'zgartirmoqchi bo'lsangiz, SerializationInfo ning SetType metodini chaqirib, kerakli Type obyektiga havolani uzatish tavsiya etiladi. SetType ni chaqirish turning to'liq nomi va aniqlash assembliyasining to'g'ri o'rnatilishini ta'minlaydi. SetType ni chaqirish namunasi ushbu bobning keyingi "Turni boshqa tur sifatida serializatsiya qilish va obyektni boshqa obyekt sifatida deserializatsiya qilish" bo'limida ko'rsatilgan.

SerializationInfo obyekti yaratilgandan va boshlang'ichlangandan keyin, formatter turning GetObjectData metodini chaqiradi va unga SerializationInfo obyektiga havolani uzatadi. GetObjectData metodi obyektni serializatsiya qilish uchun qanday ma'lumot kerakligini aniqlash va bu ma'lumotni SerializationInfo obyektiga qo'shish uchun javobgardir. GetObjectData SerializationInfo turining ko'plab overloaded AddValue metodlaridan birini chaqirib qanday ma'lumotni serializatsiya qilishni bildiradi. AddValue siz qo'shmoqchi bo'lgan har bir ma'lumot bo'lagi uchun bir marta chaqiriladi.

Dictionary Misoli

Quyidagi kod Dictionary<TKey, TValue> turi ISerializable va IDeserializationCallback interfeyslarini qanday amalga oshirishining taxminiy ko'rinishini ko'rsatadi.

[Serializable]
public class Dictionary<TKey, TValue> : ISerializable, IDeserializationCallback {
    // Private maydonlar bu yerda (ko'rsatilmagan)

    private SerializationInfo m_siInfo;  // Faqat deserializatsiya uchun

    // Maxsus konstruktor (ISerializable uchun zarur) deserializatsiyani boshqarish uchun
    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    protected Dictionary(SerializationInfo info, StreamingContext context) {
        // Deserializatsiya paytida, OnDeserialization uchun SerializationInfo ni saqlash
        m_siInfo = info;
    }

    // Serializatsiyani boshqarish uchun metod
    [SecurityCritical]
    public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
        info.AddValue("Version", m_version);
        info.AddValue("Comparer", m_comparer, typeof(IEqualityComparer<TKey>));
        info.AddValue("HashSize", (m_buckets == null) ? 0 : m_buckets.Length);
        if (m_buckets != null) {
            KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[Count];
            CopyTo(array, 0);
            info.AddValue("KeyValuePairs", array, typeof(KeyValuePair<TKey, TValue>[]));
        }
    }

    // Barcha kalit/qiymat obyektlari deserializatsiya qilingandan keyin chaqiriladigan metod
    public virtual void IDeserializationCallback.OnDeserialization(Object sender) {
        if (m_siInfo == null) return; // Hech qachon o'rnatilmagan, qaytish

        Int32 num = m_siInfo.GetInt32("Version");
        Int32 num2 = m_siInfo.GetInt32("HashSize");
        m_comparer = (IEqualityComparer<TKey>)
            m_siInfo.GetValue("Comparer", typeof(IEqualityComparer<TKey>));
        if (num2 != 0) {
            m_buckets = new Int32[num2];
            for (Int32 i = 0; i < m_buckets.Length; i++) m_buckets[i] = -1;
            m_entries = new Entry<TKey, TValue>[num2];
            m_freeList = -1;
            KeyValuePair<TKey, TValue>[] pairArray = (KeyValuePair<TKey, TValue>[])
                m_siInfo.GetValue("KeyValuePairs", typeof(KeyValuePair<TKey, TValue>[]));
            if (pairArray == null)
                ThrowHelper.ThrowSerializationException(
                    ExceptionResource.Serialization_MissingKeys);

            for (Int32 j = 0; j < pairArray.Length; j++) {
                if (pairArray[j].Key == null)
                    ThrowHelper.ThrowSerializationException(
                        ExceptionResource.Serialization_NullKey);
                Insert(pairArray[j].Key, pairArray[j].Value, true);
            }
        } else { m_buckets = null; }
        m_version = num;
        m_siInfo = null;
    }
}

Har bir AddValue metodi String nomi va ba'zi ma'lumotlarni oladi. Odatda, ma'lumotlar oddiy qiymat turi bo'ladi, masalan Boolean, Char, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal yoki DateTime. Biroq, siz String kabi obyektga havolani uzatib AddValue ni chaqirishingiz ham mumkin. GetObjectData barcha zarur serializatsiya ma'lumotlarini qo'shgandan so'ng, u formatterga qaytadi.

Eslatma

Siz serializatsiya ma'lumotlarini turningiz uchun qo'shish uchun har doim AddValue ning overloaded metodlaridan birini chaqirishingiz kerak. Agar maydonning turi ISerializable interfeysini amalga oshirsa, maydonda GetObjectData ni chaqirmang. Buning o'rniga, AddValue ni chaqirib maydonni qo'shing; formatter maydon turining ISerializable ni amalga oshirishini ko'radi va formatter siz uchun GetObjectData ni chaqiradi. Agar siz maydon obyektida GetObjectData ni chaqirgan bo'lsangiz, formatter oqimni deserializatsiya qilayotganda yangi obyektni qanday yaratishni bilmasdi.

Deserializatsiya Konstruktori

Endi serializatsiya uchun barcha ma'lumotlarni qanday o'rnatishni bilasiz, keling e'tiborimizni deserializatsiyaga qarataylik. Formatter oqimdan obyektni chiqarayotganda, yangi obyekt uchun xotira ajratadi (FormatterServices turining statik GetUninitializedObject metodi orqali). Dastlab, bu obyektning barcha maydonlari 0 yoki null ga o'rnatiladi. Keyin formatter turda ISerializable interfeysi amalga oshirilgan yoki oshirilmaganini tekshiradi. Agar bu interfeys mavjud bo'lsa, formatter GetObjectData parametrlariga mos parametrli maxsus konstruktorni chaqirishga harakat qiladi.

Agar klassingiz sealed bo'lsa, bu maxsus konstruktorni private deb e'lon qilish juda tavsiya etiladi. Bu tasodifiy chaqirishni oldini oladi va xavfsizlikni oshiradi. Aks holda, bu maxsus konstruktorni protected deb e'lon qilishingiz kerak, shunda faqat hosilaviy sinflar uni chaqira oladi. E'tibor bering, formatterlar bu maxsus konstruktorni qanday e'lon qilingan bo'lsa ham chaqira oladi.

Bu konstruktor serializatsiya paytida obyekt serializatsiya qilinganida qo'shilgan barcha qiymatlarni o'z ichiga olgan SerializationInfo obyektiga havolani oladi. Maxsus konstruktor GetBoolean, GetChar, GetByte, GetSByte, GetInt16, GetUInt16, GetInt32, GetUInt32, GetInt64, GetUInt64, GetSingle, GetDouble, GetDecimal, GetDateTime, GetString va GetValue metodlarining istalganini chaqirishi mumkin. Qaytarilgan qiymat keyin yangi obyektning maydonlarini boshlash uchun ishlatiladi.

Obyektning maydonlarini deserializatsiya qilayotganda, GetObjectData metodi sifatida qo'shilgan qiymat turiga mos Get metodini chaqirishingiz kerak. Agar oqimdagi qiymat turi olishga harakat qilayotgan turga mos kelmasa, formatter turni konvertatsiya qilish uchun IFormatterConverter obyektidan foydalanishga harakat qiladi.

Avval sanab o'tilgan Get metodlari o'rniga, maxsus konstruktor GetEnumerator ni chaqirishi mumkin, bu SerializationInfo obyektidagi barcha qiymatlar ustidan takrorlash uchun ishlatilishi mumkin bo'lgan System.Runtime.Serialization.SerializationInfoEnumerator obyektini qaytaradi.

Muhim

Maxsus konstruktordagi kod odatda unga berilgan SerializationInfo obyektidan maydonlarini chiqaradi. Maydonlar chiqarilayotganda, obyektlar to'liq deserializatsiya qilinganligiga kafolat yo'q, shuning uchun maxsus konstruktordagi kod chiqarilgan obyektlarni boshqarishga urinmasligi kerak.

Agar turningiz chiqarilgan obyekt ustida a'zolarga murojaat qilishi kerak bo'lsa (masalan, metodlarni chaqirish), turningiz OnDeserialized atributiga ega metod yaratishni yoki IDeserializationCallback interfeysining OnDeserialization metodini amalga oshirishni tavsiya qilaman (Dictionary misolida ko'rsatilganidek). Bu metod chaqirilganida, barcha obyektlar o'z maydonlariga ega bo'ladi. Biroq, bir nechta obyektlarning OnDeserialized yoki OnDeserialization metodi chaqirilish tartibiga kafolat yo'q.

Bazaviy Tur ISerializable ni Amalga Oshirmasa ISerializable ni Aniqlash

Avvalroq aytib o'tilganidek, ISerializable interfeysi juda kuchli, chunki u turning nusxalarining qanday serializatsiya va deserializatsiya qilinishini to'liq boshqarish imkonini beradi. Biroq, bu quvvat narx bilan keladi: tur endi o'zining bazaviy turining barcha maydonlarini ham serializatsiya qilish uchun javobgardir. Agar bazaviy tur ham ISerializable ni amalga oshirsa, bazaviy turining maydonlarini serializatsiya qilish oson; siz shunchaki bazaviy turining GetObjectData metodini chaqirasiz.

Biroq, ba'zan siz bazaviy turining ISerializable interfeysini amalga oshirmaydigan turning serializatsiyasini boshqarishi kerak bo'lgan turni aniqlashingiz mumkin. Bunday holda, sizning hosilaviy sinfingiz bazaviy turining maydonlarini qiymatlarni olib va ularni SerializationInfo to'plamiga qo'shib, qo'lda serializatsiya qilishi kerak. Keyin, maxsus konstruktoringizda siz to'plamdan qiymatlarni olib va bazaviy sinfning maydonlarini o'rnatishingiz kerak. Bazaviy sinfning maydonlari public yoki protected bo'lsa, bularning barchasini bajarish oson (garchi zerikarli bo'lsa ham), lekin agar bazaviy sinfning maydonlari private bo'lsa, juda qiyin yoki imkonsiz bo'lishi mumkin.

Quyidagi kod ISerializable ning GetObjectData metodi va uning konstruktorini qanday to'g'ri amalga oshirishni ko'rsatadi, shunda bazaviy turining maydonlari ham serializatsiya qilinadi.

[Serializable]
internal class Base {
    protected String m_name = "Jeff";
    public Base() { /* Turni nusxalanadigan qilish */ }
}

[Serializable]
internal sealed class Derived : Base, ISerializable {
    private DateTime m_date = DateTime.Now;
    public Derived() { /* Turni nusxalanadigan qilish */ }

    // Bu konstruktor bo'lmasa, SerializationException olamiz
    // Bu klass sealed bo'lmaganda, bu konstruktor protected bo'lishi kerak
    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    private Derived(SerializationInfo info, StreamingContext context) {
        // Sinfimiz va bazaviy sinflar uchun serializatsiya qilinadigan a'zolar to'plamini olish
        Type baseType = this.GetType().BaseType;
        MemberInfo[] mi = FormatterServices.GetSerializableMembers(baseType, context);

        // Bazaviy sinfning maydonlarini info obyektidan deserializatsiya qilish
        for (Int32 i = 0; i < mi.Length; i++) {
            // Maydonni olish va unga deserializatsiya qilingan qiymatni o'rnatish
            FieldInfo fi = (FieldInfo)mi[i];
            fi.SetValue(this, info.GetValue(baseType.FullName + "+" + fi.Name, fi.FieldType));
        }

        // Bu sinf uchun serializatsiya qilingan qiymatlarni deserializatsiya qilish
        m_date = info.GetDateTime("Date");
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
        // Bu sinf uchun kerakli qiymatlarni serializatsiya qilish
        info.AddValue("Date", m_date);

        // Sinfimiz va bazaviy sinflar uchun serializatsiya qilinadigan a'zolar to'plamini olish
        Type baseType = this.GetType().BaseType;
        MemberInfo[] mi = FormatterServices.GetSerializableMembers(baseType, context);

        // Bazaviy sinfning maydonlarini info obyektiga serializatsiya qilish
        for (Int32 i = 0; i < mi.Length; i++) {
            // Maydon nomini bazaviy turning to'liq nomi bilan prefiks qilish
            info.AddValue(baseType.FullName + "+" + mi[i].Name,
                ((FieldInfo)mi[i]).GetValue(this));
        }
    }

    public override String ToString() {
        return String.Format("Name={0}, Date={1}", m_name, m_date);
    }
}

Ushbu kodda bazaviy sinf Base faqat SerializableAttribute maxsus atributi bilan belgilangan. Base dan hosil bo'lgan Derived sinfi ham SerializableAttribute atributi bilan belgilangan va ISerializable interfeysini ham amalga oshiradi. Vaziyatni yanada qiziqarli qilish uchun, ikkala sinfda ham m_name deb nomlangan String maydoni aniqlangan. SerializationInfo ning AddValue metodini chaqirayotganda, bir xil nomdagi bir nechta qiymat qo'shib bo'lmaydi. Yuqoridagi kod bu vaziyatni har bir maydonnomini sinfning nomi bilan prefiks qilib hal qiladi. Masalan, GetObjectData metodi AddValue ni chaqirib Base ning m_name maydonini serializatsiya qilganida, qiymat nomi "Base+m_name" sifatida yoziladi.

Oqim Kontekstlari (Streaming Contexts)

Avvalroq aytib o'tilganidek, serializatsiya qilingan obyektlar to'plami uchun ko'plab manzillar mavjud: bir xil jarayon, bir xil mashindagi boshqa jarayon, boshqa mashindagi boshqa jarayon va hokazo. Ba'zi kamdan-kam holatlarda, obyekt qaerga deserializatsiya qilinishini bilishni xohlashi mumkin, shunda u o'z holatini boshqacha chiqarishi mumkin. Masalan, Windows semafori obyektini o'rab olgan obyekt o'zi bir xil jarayonga deserializatsiya qilinishini bilsa, yadro identifikatorini serializatsiya qilishga qaror qilishi mumkin, chunki yadro identifikatorlari jarayon ichida amal qiladi. Biroq, agar obyekt bir xil mashinadagi boshqa jarayonga deserializatsiya qilinishini bilsa, semaforning satr nomini serializatsiya qilishga qaror qilishi mumkin. Nihoyat, agar obyekt boshqa mashinadagi jarayonga deserializatsiya qilinishini bilsa, istisno chiqarishga qaror qilishi mumkin, chunki semafor faqat bitta mashinada amal qiladi.

Ushbu bobda avvalroq tilga olingan bir nechta metodlar StreamingContext qabul qiladi. StreamingContext strukturasi faqat ikkita public faqat-o'qish xususiyatlariga ega bo'lgan juda oddiy qiymat turi:

A'zo nomiA'zo turiTavsif
StateStreamingContextStatesSerializatsiya/deserializatsiya qilinayotgan obyektlarning manba yoki manzilini ko'rsatuvchi bit bayroqlar to'plami.
ContextObjectFoydalanuvchi tomonidan belgilangan istalgan kontekst ma'lumotini o'z ichiga olgan obyektga havola.

StreamingContext strukturasini olgan metod State xususiyatining bit bayroqlarini tekshirib serializatsiya/deserializatsiya qilinayotgan obyektlarning manba yoki manzilini aniqlashi mumkin. Quyidagi jadvalda mumkin bo'lgan bit bayroq qiymatlari ko'rsatilgan:

Bayroq nomiBayroq qiymatiTavsif
CrossProcess0x0001Manba yoki manzil bir xil mashindagi boshqa jarayondir.
CrossMachines0x0002Manba yoki manzil boshqa mashinaladadir.
File0x0004Manba yoki manzil fayldir. Bir xil jarayon ma'lumotlarni deserializatsiya qiladi deb taxmin qilmang.
Persistence0x0008Manba yoki manzil ma'lumotlar bazasi yoki fayl kabi ombor. Bir xil jarayon ma'lumotlarni deserializatsiya qiladi deb taxmin qilmang.
Remoting0x0010Manba yoki manzil noma'lum joyga remoting qilmoqda. Joy bir xil mashinada yoki boshqa mashinada bo'lishi mumkin.
Other0x0020Manba yoki manzil noma'lum.
Clone0x0040Obyekt grafi klonlanmoqda. Bir xil jarayon ma'lumotlarni deserializatsiya qiladi deb taxmin qilish mumkin va shuning uchun identifikatorlar va boshqariladigan bo'lmagan resurslarga murojaat qilish xavfsizdir.
CrossAppDomain0x0080Manba yoki manzil boshqa AppDomaindir.
All0x00FFManba yoki manzil yuqoridagi kontekstlarning istalganisi bo'lishi mumkin. Bu standart kontekstdir.

Endi bu ma'lumotni qanday olish va o'rnatishni bilasiz, keling uni qanday ishlatishni muhokama qilaylik. IFormatter interfeysi (u BinaryFormatter va SoapFormatter turlari tomonidan amalga oshirilgan) Context deb nomlangan o'qish/yozish StreamingContext xususiyatini aniqlaydi. Formatter yaratilganida, formatter o'zining Context xususiyatini StreamingContextStates All ga o'rnatilgan va qo'shimcha holat obyektiga havola null ga o'rnatilgan holda boshlaydi.

Formatter yaratilgandan keyin, StreamingContextStates bit bayroqlarning istalganisidan foydalanib StreamingContext strukturasini yaratishingiz va ixtiyoriy ravishda kerakli qo'shimcha kontekst ma'lumotini o'z ichiga olgan obyektga havolani berishingiz mumkin. Endi qilishingiz kerak bo'lgan narsa formatterning Context xususiyatini ushbu yangi StreamingContext obyekti bilan o'rnatib, keyin formatterning Serialize yoki Deserialize metodlarini chaqirishdir. Formatterga grafdagi barcha obyektlarni klonlash maqsadida serializatsiya/deserializatsiya qilayotganingizni qanday aytishni ko'rsatuvchi kod ushbu bobda avvalroq taqdim etilgan DeepClone metodida ko'rsatilgan.

Turni Boshqa Tur Sifatida Serializatsiya Qilish va Obyektni Boshqa Obyekt Sifatida Deserializatsiya Qilish

.NET Framework ning serializatsiya infrastrukturasi juda boy va ushbu bo'limda biz dasturchi turni boshqa turga serializatsiya yoki deserializatsiya qila oladigan turni qanday loyihalashi mumkinligini muhokama qilamiz. Bu qiziqarli bo'lgan ba'zi misollar:

  • Ba'zi turlar (masalan, System.DBNull va System.Reflection.Missing) har bir AppDomain uchun faqat bitta nusxaga ega bo'lish uchun mo'ljallangan. Bu turlar ko'pincha singletonlar deb ataladi. DBNull obyektiga havolangiz bo'lsa, uni serializatsiya va deserializatsiya qilish AppDomain da yangi DBNull obyekti yaratilishiga olib kelmasligi kerak. Deserializatsiyadan keyin qaytarilgan havola AppDomain da allaqachon mavjud DBNull obyektiga ishora qilishi kerak.
  • Ba'zi turlar (masalan, System.Type, System.Reflection.Assembly va boshqa refleksiya turlari) har bir turga, assembliyaga, a'zoga va hokazoga bitta nusxaga ega. Massivni tasavvur qiling, unda har bir element bitta MemberInfo obyektiga murojaat qiladi. Agar beshta element bitta MemberInfo obyektiga ishora qilsa, bu massivni serializatsiya va deserializatsiya qilgandan keyin, beshta element bitta MemberInfo obyektiga ishora qilishi kerak.
  • Masofaviy boshqariladigan obyektlar uchun CLR server obyekti haqidagi ma'lumotlarni serializatsiya qiladi, bu klientda deserializatsiya qilinganida CLR proksi obyektini yaratishga sabab bo'ladi.

Keling, singleton turini to'g'ri serializatsiya va deserializatsiya qilishni ko'rsatadigan kodni ko'rib chiqaylik.

// Har bir AppDomain uchun faqat bitta nusxa bo'lishi kerak
[Serializable]
public sealed class Singleton : ISerializable {
    // Bu turning yagona nusxasi
    private static readonly Singleton s_theOneObject = new Singleton();

    // Bu yerda nusxa maydonlari
    public String Name = "Jeff";
    public DateTime Date = DateTime.Now;

    // Bu turni singleton qilish uchun private konstruktor
    private Singleton() { }

    // Singletonga havolani qaytaruvchi metod
    public static Singleton GetSingleton() { return s_theOneObject; }

    // Singletonni serializatsiya qilayotganda chaqiriladigan metod
    // Men Explicit Interface Method Implementation dan foydalanishni tavsiya qilaman
    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
        info.SetType(typeof(SingletonSerializationHelper));
        // Boshqa qiymatlar qo'shish kerak emas
    }

    [Serializable]
    private sealed class SingletonSerializationHelper : IObjectReference {
        // Bu obyekt (hech qanday maydonsiz) deserializatsiya qilingandan keyin chaqiriladigan metod
        public Object GetRealObject(StreamingContext context) {
            return Singleton.GetSingleton();
        }
    }

    // ESLATMA: Maxsus konstruktor hech qachon chaqirilmagani uchun zarur EMAS
}

Bu kodni tushunish uchun uni bosqichma-bosqich ko'rib chiqaylik. Singleton turi AppDomain ga yuklanganda, CLR uning statik konstruktorini chaqiradi, bu Singleton obyektini yaratadi va unga havolani statik maydon s_theOneObject da saqlaydi. Singleton sinfi hech qanday public konstruktor taklif qilmaydi, bu boshqa kodning ushbu sinfning boshqa nusxalarini yaratishiga to'sqinlik qiladi.

Quyidagi test kodi serializatsiya va deserializatsiyaning to'g'ri ishlashini tekshiradi:

private static void SingletonSerializationTest() {
    // Bitta Singleton obyektiga murojaat qiluvchi bir nechta elementli massiv yaratish
    Singleton[] a1 = { Singleton.GetSingleton(), Singleton.GetSingleton() };
    Console.WriteLine("Do both elements refer to the same object? "
        + (a1[0] == a1[1])); // "True"

    using (var stream = new MemoryStream()) {
        BinaryFormatter formatter = new BinaryFormatter();

        // Massiv elementlarini serializatsiya va deserializatsiya qilish
        formatter.Serialize(stream, a1);
        stream.Position = 0;
        Singleton[] a2 = (Singleton[])formatter.Deserialize(stream);

        // Ishlashini isbotlash:
        Console.WriteLine("Do both elements refer to the same object? "
            + (a2[0] == a2[1])); // "True"
        Console.WriteLine("Do all  elements refer to the same object? "
            + (a1[0] == a2[0])); // "True"
    }
}

Birinchi Singleton serializatsiya qilinayotganda, formatter Singleton turi ISerializable interfeysini amalga oshirishini aniqlaydi va GetObjectData metodini chaqiradi. Bu metod SetType ni chaqirib SingletonSerializationHelper turini uzatadi, bu formatterga Singleton obyekti o'rniga SingletonSerializationHelper obyektini serializatsiya qilishni buyuradi. AddValue chaqirilmagani uchun oqimga hech qanday qo'shimcha maydon ma'lumoti yozilmaydi.

Deserializatsiyada formatter SingletonSerializationHelper obyektini deserializatsiya qilishga harakat qiladi. Bu tur System.Runtime.Serialization.IObjectReference interfeysini amalga oshiradi:

public interface IObjectReference {
    Object GetRealObject(StreamingContext context);
}

Formatter bu interfeysni amalga oshirilganini ko'rganda, GetRealObject metodini chaqiradi. Bu metod siz haqiqatan ham xohlagan obyektga havolani qaytaradi. Mening misolimda SingletonSerializationHelper turi GetRealObject da AppDomain da allaqachon mavjud Singleton obyektiga havolani qaytaradi. Shunday qilib, formatterning Deserialize metodi qaytganida, a2 massivi ikkita elementni o'z ichiga oladi, ikkalasi ham AppDomain ning Singleton obyektiga ishora qiladi. Deserializatsiyaga yordam berish uchun ishlatilgan SingletonSerializationHelper obyekti darhol erishib bo'lmaydigan bo'lib qoladi va kelajakda axlat yig'iladi.

Serializatsiya Surrogatlari

Hozirgacha men turning o'z implementatsiyasini o'zgartirib turni qanday serializatsiya va deserializatsiya qilishini boshqarishni muhokama qildim. Biroq, formatterlar turning implementatsiyasiga tegishli bo'lmagan kodga ham turni serializatsiya va deserializatsiya qilish ustidan nazorat o'rnatish imkonini beradi. Ilova kodi turining xatti-harakatini bekor qilishni xohlashining ikkita asosiy sababi bor:

  • Dastlab serializatsiya qilinadigan qilib loyihalanmagan turni serializatsiya qilish imkoniyatini beradi.
  • Turning bir versiyasini turning boshqa versiyasiga moslashtirish usulini taqdim etadi.

Asosan, bu mexanizmni ishlashi uchun siz avval mavjud turni serializatsiya va deserializatsiya qilish uchun zarur amallarni o'z zimmasiga oladigan "surrogat tur" ni aniqlaysiz. Keyin surrogat turning nusxasini formatter bilan ro'yxatdan o'tkazasiz, formatterga surrogat turning qaysi mavjud tur uchun javobgar ekanligini aytasiz. Formatter mavjud turning nusxasini serializatsiya yoki deserializatsiya qilishga harakat qilganini aniqlasa, surrogat obyektingiz tomonidan aniqlangan metodlarni chaqiradi.

Serializatsiya surrogat turi System.Runtime.Serialization.ISerializationSurrogate interfeysini amalga oshirishi kerak:

public interface ISerializationSurrogate {
    void GetObjectData(Object obj, SerializationInfo info, StreamingContext context);

    Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context,
        ISurrogateSelector selector);
}

Keling, bu interfeysdan foydalanadigan misolni ko'rib chiqaylik. Dasturingizda foydalanuvchining kompyuterida mahalliy bo'lgan qiymatlarni o'z ichiga olgan DateTime obyektlari mavjud deyaylik. Agar DateTime obyektlarini oqimga serializatsiya qilmoqchi bo'lsangiz, lekin qiymatlarning universal vaqtda serializatsiya qilinishini istasangiz? Bu sizga ma'lumotlarni tarmoq oqimi orqali dunyoning boshqa qismidagi mashinaga yuborish va DateTime qiymatining to'g'ri bo'lishini ta'minlash imkonini beradi. Quyida surrogat klassni qanday aniqlash ko'rsatilgan.

internal sealed class UniversalToLocalTimeSerializationSurrogate : ISerializationSurrogate {
    public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context) {
        // DateTime ni mahalliydan UTC ga aylantirish
        info.AddValue("Date", ((DateTime)obj).ToUniversalTime().ToString("u"));
    }

    public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context,
        ISurrogateSelector selector) {
        // DateTime ni UTC dan mahalliyga aylantirish
        return DateTime.ParseExact(info.GetString("Date"), "u", null).ToLocalTime();
    }
}

GetObjectData metodi ISerializable interfeysining GetObjectData metodi kabi ishlaydi. Yagona farq shundaki, ISerializationSurrogate ning GetObjectData metodi qo'shimcha bir parametrga ega: serializatsiya qilinishi kerak bo'lgan "haqiqiy" obyektga havola.

SetObjectData metodi DateTime obyektini deserializatsiya qilish uchun chaqiriladi. U SerializationInfo to'plamidan satr sanani oladi, uni universal formatdan tahlil qiladi va keyin mahalliy vaqtga aylantiradi.

Quyidagi kod surrogat sinfi bilan formatterni qanday sinab ko'rishni ko'rsatadi:

private static void SerializationSurrogateDemo() {
    using (var stream = new MemoryStream()) {
        // 1. Kerakli formatterni yaratish
        IFormatter formatter = new SoapFormatter();

        // 2. SurrogateSelector obyektini yaratish
        SurrogateSelector ss = new SurrogateSelector();

        // 3. Surrogat selektorga DateTime obyektlari uchun bizning surrogatimizdan
        //    foydalanishni aytish
        ss.AddSurrogate(typeof(DateTime), formatter.Context,
            new UniversalToLocalTimeSerializationSurrogate());

        // ESLATMA: AddSurrogate bir nechta surrogatlarni ro'yxatdan o'tkazish uchun
        //          bir nechta marta chaqirilishi mumkin

        // 4. Formatterga bizning surrogat selektorimizdan foydalanishni aytish
        formatter.SurrogateSelector = ss;

        // Mashinadagi mahalliy vaqtni ifodalovchi DateTime yaratish va serializatsiya qilish
        DateTime localTimeBeforeSerialize = DateTime.Now;
        formatter.Serialize(stream, localTimeBeforeSerialize);

        // Oqim universal vaqtni satr sifatida ko'rsatadi, ishlashini isbotlash uchun
        stream.Position = 0;
        Console.WriteLine(new StreamReader(stream).ReadToEnd());

        // Universal vaqt satrini deserializatsiya qilish va mahalliy DateTimega aylantirish
        stream.Position = 0;
        DateTime localTimeAfterDeserialize = (DateTime)formatter.Deserialize(stream);

        // To'g'ri ishlaganini isbotlash:
        Console.WriteLine("LocalTimeBeforeSerialize  ={0}", localTimeBeforeSerialize);
        Console.WriteLine("LocalTimeAfterDeserialize={0}", localTimeAfterDeserialize);
    }
}

1 dan 4 gacha qadamlar bajarilgandan keyin, formatter ro'yxatdan o'tgan surrogatdan foydalanishga tayyor. Formatterning Serialize metodi chaqirilganida, har bir obyektning turi SurrogateSelector tomonidan saqlanayotgan to'plamda qidiriladi. Agar mos tur topilsa, ISerializationSurrogate obyektining GetObjectData metodi chaqirilib oqimga yozilishi kerak bo'lgan ma'lumotni oladi.

Formatterning Deserialize metodi chaqirilganida, deserializatsiya qilinayotgan obyektning turi formatterning SurrogateSelector da qidiriladi va agar mos tur topilsa, ISerializationSurrogate obyektining SetObjectData metodi chaqirilib deserializatsiya qilinayotgan obyektning maydonlarini o'rnatadi.

Ichki tarzda, SurrogateSelector obyekti private xesh jadvalini saqlaydi. AddSurrogate chaqirilganida, Type va StreamingContext kalitni tashkil qiladi va ISerializationSurrogate obyekti qiymatdir. Agar bir xil Type/StreamingContext kalitga ega juftlik allaqachon mavjud bo'lsa, AddSurrogate ArgumentException chiqaradi. Kalitga StreamingContext ni kiritish orqali, siz DateTime obyektini faylga serializatsiya/deserializatsiya qilishni biladigan bitta surrogat tur obyektini va DateTime obyektini boshqa jarayonga serializatsiya/deserializatsiya qilishni biladigan boshqa surrogat tur obyektini ro'yxatdan o'tkazishingiz mumkin.

Eslatma

BinaryFormatter klassida bir-biriga murojaat qiladigan obyektlar bilan surrogatni serializatsiya qilishning oldini oladigan xato (bug) mavjud. Buni tuzatish uchun ISerializationSurrogate obyektingizga havolani FormatterServices ning statik GetSurrogateForCyclicalReference metodiga uzatishingiz kerak. Bu metod ISerializationSurrogate qaytaradi, uni SurrogateSelector ning AddSurrogate metodiga uzatishingiz mumkin. Biroq, GetSurrogateForCyclicalReference metodidan foydalanganingizda, surrogatning SetObjectData metodi SetObjectData ning obj parametri bilan ko'rsatilgan obyekt ichidagi qiymatni o'zgartirishi va nihoyat chaqiruvchi metodga null yoki obj ni qaytarishi kerak.

Surrogat Selektorlar Zanjiri

Bir nechta SurrogateSelector obyektlarini bir-biriga bog'lash (zanjirlash) mumkin. Masalan, turlarni proksilarga serializatsiya qilish yoki AppDomainlar o'rtasida uzatish uchun ishlatiladigan serializatsiya surrogatlari to'plamiga ega bitta SurrogateSelector ob'yektingiz bo'lishi mumkin. Bundan tashqari, 1-versiya turlarni 2-versiya turlarga aylantirish uchun ishlatiladigan serializatsiya surrogatlari to'plamini o'z ichiga olgan alohida SurrogateSelector obyektingiz bo'lishi mumkin.

Agar formatterning foydalanishini xohlagan bir nechta SurrogateSelector obyektlaringiz bo'lsa, ularni bog'langan ro'yxatga zanjirlashingiz kerak. SurrogateSelector turi ISurrogateSelector interfeysini amalga oshiradi, u zanjirlash bilan bog'liq uchta metodni aniqlaydi:

public interface ISurrogateSelector {
    void ChainSelector(ISurrogateSelector selector);
    ISurrogateSelector GetNextSelector();
    ISerializationSurrogate GetSurrogate(Type type, StreamingContext context,
        out ISurrogateSelector selector);
}

ChainSelector metodi ISurrogateSelector obyektini ishlov berilayotgan ISurrogateSelector obyektining ('this' obyekti) darhol keyingi joyiga qo'yadi. GetNextSelector metodi zanjirdagi keyingi ISurrogateSelector obyektiga havolani yoki agar ishlov berilayotgan obyekt zanjirning oxiri bo'lsa null ni qaytaradi.

GetSurrogate metodi 'this' tomonidan aniqlangan ISurrogateSelector obyektida Type/StreamingContext juftini qidiradi. Agar juftlik topilmasa, keyingi ISurrogateSelector obyektiga murojaat qilinadi va hokazo. Agar mos kelish topilsa, GetSurrogate turning serializatsiya/deserializatsiyasini boshqaradigan ISerializationSurrogate obyektini qaytaradi. Zanjirdagi ISurrogateSelector obyektlarining hech birida Type/StreamingContext juftlik uchun mos topilmasa, GetSurrogate null qaytaradi.

Eslatma

FCL ISurrogateSelector interfeysi va bu interfeysni amalga oshiruvchi SurrogateSelector turini aniqlaydi. Biroq, biror kimsa ISurrogateSelector interfeysini amalga oshiruvchi o'z turini aniqlashiga ehtiyoj tug'ilishi juda kam uchraydi. O'z turini aniqlashning yagona sababi bir turdan boshqasiga moslashtirish ustidan ko'proq moslashuvchanlikka ega bo'lish zaruratidir. Masalan, muayyan bazaviy sinfdan meros bo'lgan barcha turlarni maxsus tarzda serializatsiya qilishni xohlashingiz mumkin. System.Runtime.Remoting.Messaging.RemotingSurrogateSelector klassi buning ajoyib misolidir.

Obyektni Deserializatsiya Qilayotganda Assembly va/yoki Turni Almashtirish

Obyektni serializatsiya qilayotganda, formatterlar turning to'liq nomini va turni aniqlagan assembliyaning to'liq nomini chiqaradi. Obyektni deserializatsiya qilayotganda, formatterlar bu ma'lumotdan aniq qanday turdagi obyektni yaratish va boshlashni aniqlash uchun foydalanadi. ISerializationSurrogate interfeysi haqidagi avvalgi muhokama sizga muayyan tur uchun serializatsiya va deserializatsiya vazifalarini o'z zimmasiga olish imkonini berdi. ISerializationSurrogate interfeysini amalga oshiruvchi tur muayyan assembliyaning muayyan turiga bog'langan.

Biroq, ISerializationSurrogate mexanizmi yetarli moslashuvchanlik bermagan paytlar ham bor. Quyida obyektni serializatsiya qilinganidagi turdan boshqa turga deserializatsiya qilish foydali bo'lishi mumkin bo'lgan ba'zi stsenariylar:

  • Dasturchi turning implementatsiyasini bir assembliyadan boshqasiga ko'chirishga qaror qilishi mumkin. Masalan, assembliyaning versiya raqami o'zgarishlari yangi assembliyani asl assembliyadan farqli qiladi.
  • Serverdagi obyekt klientga yuboriladigan oqimga serializatsiya qilinadi. Klient oqimni qayta ishlaganida, u obyektni serverning obyektiga metod chaqiruvlarini masofaviy bajarishni biladigan butunlay boshqa turdagi obyektga deserializatsiya qilishi mumkin.
  • Dasturchi turning yangi versiyasini yaratadi. Avvalroq serializatsiya qilingan obyektlarni turning yangi versiyasiga deserializatsiya qilishni xohlaymiz.

System.Runtime.Serialization.SerializationBinder klassi obyektni boshqa turga deserializatsiya qilishni juda osonlashtiradi. Buni amalga oshirish uchun avval abstrakt SerializationBinder turidan hosil bo'lgan o'z turini aniqlaysiz. Quyidagi kodda, assembliyangizning 1.0.0.0 versiyasi Ver1 deb nomlangan klassni aniqlagan va assembliyangizning yangi versiyasi Ver1ToVer2SerializationBinder klassini va Ver2 deb nomlangan klassni aniqlaydi deb faraz qilaylik.

internal sealed class Ver1ToVer2SerializationBinder : SerializationBinder {
    public override Type BindToType(String assemblyName, String typeName) {
        // Har qanday Ver1 obyektini v1.0.0.0 dan Ver2 obyektiga deserializatsiya qilish

        // Ver1 turini aniqlagan assembliya nomini hisoblash
        AssemblyName assemVer1 = Assembly.GetExecutingAssembly().GetName();
        assemVer1.Version = new Version(1, 0, 0, 0);

        // Agar v1.0.0.0 dan Ver1 obyektini deserializatsiya qilayotgan bo'lsa,
        // uni Ver2 obyektiga aylantirish
        if (assemblyName == assemVer1.ToString() && typeName == "Ver1")
            return typeof(Ver2);

        // Aks holda, so'ralayotgan turni qaytarish
        return Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
    }
}

Endi formatterni yaratgandan keyin, Ver1ToVer2SerializationBinder nusxasini yarating va formatterning Binder o'qish/yozish xususiyatini binder obyektiga murojaat qiladigan qilib o'rnating. Binder o'rnatilgandan keyin, endi formatterning Deserialize metodini chaqirishingiz mumkin. Deserializatsiya paytida formatter binder o'rnatilganini ko'radi. Har bir obyekt deserializatsiya qilinayotganda, formatter binderning BindToType metodini chaqirib, unga formatter deserializatsiya qilmoqchi bo'lgan assembliya nomi va turni uzatadi. Bu nuqtada, BindToType aslida qanday tur yaratilishi kerakligini hal qiladi va bu turni qaytaradi.

Eslatma

SerializationBinder klassi BindToName metodini bekor qilish orqali obyektni serializatsiya qilayotganda assembliya/tur ma'lumotini o'zgartirish imkoniyatini ham beradi:

public virtual void BindToName(Type serializedType,
    out string assemblyName, out string typeName)

Serializatsiya paytida formatter bu metodni chaqirib, serializatsiya qilmoqchi bo'lgan turni uzatadi. Siz keyin (ikkita out parametrlari orqali) o'rniga serializatsiya qilmoqchi bo'lgan assembliya va turni qaytarishingiz mumkin. Agar siz null va null qaytarsangiz (standart implementatsiya shunday qiladi), hech qanday o'zgarish amalga oshirilmaydi.