Skip to content

Лабораториска вежба 9

Tomche Delev edited this page May 16, 2013 · 3 revisions

Сериализација и зачувување сериализибилен објект во датотека

Што е тоа сериализација?

Сериализација е процес на конвертирање на даден тип во состојба во која ќе може инстанци од овој тип:

  • да се зачувуваат на постојана меморија како диск (во датотека, база на податоци)
  • да се пренесуваат преку мрежа
  • да се пренесуваат по вредност надвор од границите на системот.

Зачувување на сериализибилен објект во датотека

Проблем

Потреба од зачувување на сериализибилен објект и неговата состојба во датотека, а потоа негова десериализација и вчитување од датотека.

Решение

Ќе употребиме formatter за да го сериализираме објектот и запишеме во System.IO.FileStream објект. Кога имаме потреба да го вчитаме објектот, ќе го искористиме истиот тип на formatter за да ги вчитаме сериализираните податоци од датотеката и го десериализираме објектот. Библиотеките во .NET Framework содржат класи со следните имплементации на на formatter за сериализација на објекти во бинарен или SOAP формат:

  • System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
  • System.Runtime.Serialization.Formatters.Soap.SoapFormatter (Овој вид на форматер нема да го обработиме.)

Како функционира

Со користење на класите BinaryFormatter и SoapFormatter може да се сериализира инстанца од секој сериализибилен тип. Класата BinaryFormatter продуцира бинарен податочен тек со кој се репрезентира состојбата на објектот. SoapFormatter класата продуцира SOAP документ. И двете BinaryFormatter и SoapFormatter класите го имплементираат интерфејсот System.Runtime.Serialization.IFormatter, кој дефинира два методи: Serialize и Deserialize.

Методот Serialize прима како аргументи референца од System.IO.Stream и референца од објектот, го сериализира објектот и го запишува во податочниот тек. Методот Deserialize прима како аргумент рефернца од Stream, го чита сериализираниот објект од текот и враќа како резултат референца кон десерилизираниот објект. Притоа мора да се кастира резултантната референца кон објктот од соодветиот тип.

Изворен код

Во следниот пример е прикажана употребата на BinaryFormatter за да се сериализира System.Collections.ArrayList објект кој содржи листа од стрингови (имиња на луѓе) во датотека. Потоа објектот од тип ArrayList е десериализиран и неговата содржина е прикажана на конзолата.

using System;
using System.IO;
using System.Collections;
using System.Runtime.Serialization.Formatters.Binary;
namespace Lab9
{
    class SerializacijaNaLista
    {
        // Serialize an ArrayList object to a binary file.
        private static void BinarySerialize(ArrayList list)
        {
            using (FileStream str = File.Create("people.bin"))
            {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(str, list);
            }
        }
        // Deserialize an ArrayList object from a binary file.
        private static ArrayList BinaryDeserialize()
        {
            ArrayList people = null;
            using (FileStream str = File.OpenRead("people.bin"))
            {
                BinaryFormatter bf = new BinaryFormatter();
                people = (ArrayList)bf.Deserialize(str);
            }
            return people;
        }

        public static void Main()
        {
            // Create and configure the ArrayList to serialize.
            ArrayList people = new ArrayList();
            people.Add("Graeme");
            people.Add("Lin");
            people.Add("Andy");
            // Serialize the list to a file in both binary and SOAP form.
            BinarySerialize(people);
            // Rebuild the lists of people from the binary and SOAP
            // serializations and display them to the console.
            ArrayList binaryPeople = BinaryDeserialize();
            Console.WriteLine("Binary people:");
            foreach (string s in binaryPeople)
            {
                Console.WriteLine("\t" + s);
            }
            // Wait to continue.
            Console.WriteLine("\nMain method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Имплементирање на кориснички дефиниран сериализибилен тип

Проблем

Потреба да се имплементира сопствен тип кој е сериализибилен, со што се добиваат бенефити кои ги нуди сериализацијата.

Решение

За сериализација на едноставни типови, само додадете го атрибутот System.SerializableAttribute во декларацијата на типот. За типови кои се покомплексни, или каде што треба да се контролира структурата на податоците кои што се сериализираат, се имплементира интерфејсот System.Runtime.Serialization.ISerializable.

Како функционира

Претходно покажавме како се сериализира и десериализира објект со користење на форматер класите од библитеките во .NET Framework. Меѓутоа, сите типови не се сериализибилни. за да се имплементира кориснички дефиниран тип кој што ќе биде сериализибилен, мора да се додаде атрибутот SerializableAttribute ([Serializble]) во декларацијата на типот. Се додека сите податочни полиња во овој тип се серилизбилни, се што треба да се направи е да се додаде SerializableAttribute во декларацијата на типот. Ако се имплементира кориснички дефинирана класа која наследува од некоја друга основна класа, основната класа исто така треба да биде сериализибилна.

Секоја formatter класа се состои од логика потребна да ги сериализира сите типови анотирани со SerializableAttribute и точно ќе ги сериализира сите public, protected и private полиња. Може да ги исклучиме одредени полиња од процесот на сериализација со додавње на атрибутот System.NonSerializedAttribute на соодветните полиња. Како правило, од сериализација треба да се исклучуваат следниве полиња:

  • полиња кои содржат несериализибилни податочни типови
  • полиња кои содржат вредности кои може да бидат неправилни во моментот кога објектот се десериализира, како што се конекции до база, адреса на меморија, ID на нитки и слични ресурси
  • полиња кои содржат чуствителни или тајни информации, како што се лозинки, клучеви за енкрипција и останати лични детали за луѓе или организации
  • полиња кои содржат податоци кои може лесно да се ре-креираат или извлечат од други извори, посебно кога се работи за големо количество податоци

Ако исклучите некои полиња од сериализација, мора да го имплементирате тој тип за да компензирате за сите полиња кои нема да се иницијализираат во процесот на десериализација.

За повеќето од кориснички дефинираните типови, следниот механизам е доволен за да ги исполни потребите за сериализација. Ако ви е потребна поголема контрола во процесот на сериализација, може да го имплементирате интерфејсот ISerializable. Класите за форматирање користат различна логика кога сериализираат и десериализираат инстанци од типови кои го имплементираат ISerializable.

За да се имплементира точно ISerializable, треба да се направи следното:

  • да се декларира декат вашиот тип го имплементира ISerializable
  • да се додаде атрибутот SerializableAttribute во декларацијата на типот
  • да се имплементира методот ISerializable.GetObjectData (се користи при сериализација), кој прима аргументи од типот System.Runtime.Serialization, SerializationInfo и System.Runtime.Serialization.StreamingContext.
  • да се имплементира не јавен (private или protected) конструктор кој прима исти аргументи како и методот GetObjectData.

За време на процесот на сериализација, форматерот го повикува методот GetObjectData и му пренесува референци од SerializationInfo и StreamingContext како аргументи. Вашиот тип мора да го пополни објектот од типот SerializationInfo со податоците кои сакате да ги сериализирате.

Ако креирате сериализибилна класа која наследува од основна класа која исто така имплементира ISerializable, вашиот метод GetObjectData и конструкторот за десериализација мора да ги повикуваат еквивалентните методи од основната класа. Класата SerializationInfo се однесува како листа од клуч/вредност парови и содржи метод AddValue за додавање на вакви парови. Во секој повик кон AddValue, мора да се одреди име на парот.

Ова име се користи за време на десериализацијата за да се извлече вредноста на ова поле. Методот AddValue има 16 преоптоварени дефиниции кои овозможуваат додавање на различни податочни типови во SerializationInfo објектот. Објектот StreamingContext дава информации за целта и дестинцијата на сериализираните податоци, со што ви дава можност за кои податоци да ги сериализриате. Кога форматерот десериализира инстанци од вашиот тип, го повикува конструкторот за десериализација, со што му ги пренесува референците одSerializationInfo и StreamingContext објектите како аргументи.

Вашиот тип мора да ги извлече сериализираните податоци од SerializationInfo објектот со употреба на соодветните Get* методи; на пример, со користење на GetString, GetInt32, или GetBoolean. За време на десериализацијата, објектот StreamingContext дава информации за изворот на сериализираните податоци, со што ви дава можност да ја искористите истата логика кога сте ги сериализирале податоците.

Изворен код

Следниот код демонстрира сериализација на класа Employee која го имплементира интерфејсот ISerializable. Во овој пример, од класата Employee нема да го сериализираме полето за адреса ако дадениот StreamingContext објект означува дека дестинацијата на сериализацијата ќе биде во датотека.

Main методот покажува сериализација и десериализација на објект од класата Employee.

using System;
using System.IO;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace Lab9
{
    [Serializable]
    public class Employee : ISerializable
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Address;
        // Simple Employee constructor.
        public Employee(string name, int age, string address)
        {
            this.Name = name;
            this.Age = age;
            this.Address = address;
        }
        // Constructor required to enable a formatter to deserialize an
        // Employee object. You should declare the constructor private or at
        // least protected to ensure it is not called unnecessarily.
        private Employee(SerializationInfo info, StreamingContext context)
        {
            // Extract the name and age of the employee, which will always be
            // present in the serialized data regardless of the value of the
            // StreamingContext.
            Name = info.GetString("Name");
            Age = info.GetInt32("Age");
            // Attempt to extract the employee's address and fail gracefully
            // if it is not available.
            try
            {
                Address = info.GetString("Address");
            }
            catch (SerializationException)
            {
                Address = null;
            }
        }

        // Declared by the ISerializable interface, the GetObjectData method
        // provides the mechanism with which a formatter obtains the object
        // data that it should serialize.
        public void GetObjectData(SerializationInfo inf, StreamingContext con)
        {
            // Always serialize the employee's name and age.
            inf.AddValue("Name", Name);
            inf.AddValue("Age", Age);
        
            // Don't serialize the employee's address if the StreamingContext
            // indicates that the serialized data is to be written to a file.
            if ((con.State & StreamingContextStates.File) == 0)
            {
                inf.AddValue("Address", Address);
            }
        }
        // Override Object.ToString to return a string representation of the
        // Employee state.
        public override string ToString()
        {
            StringBuilder str = new StringBuilder();
            str.AppendFormat("Name: {0}\r\n", Name);
            str.AppendFormat("Age: {0}\r\n", Age);
            str.AppendFormat("Address: {0}\r\n", Address);
            return str.ToString();
        }
    }
    // A class to demonstrate the use of Employee.
    public class TestSerialization
    {
        public static void Main(string[] args)
        {
            // Create an Employee object representing Roger.
            Employee roger = new Employee("Roger", 56, "London");
            // Display Roger.
            Console.WriteLine(roger);
            // Serialize Roger specifying another application domain as the
            // destination of the serialized data. All data including Roger's
            // address is serialized.
            Stream str = File.Create("roger.bin");
            BinaryFormatter bf = new BinaryFormatter();
            bf.Context = new
            StreamingContext(StreamingContextStates.CrossAppDomain); 
            bf.Serialize(str, roger);
            
            str.Close();
            // Deserialize and display Roger.
            str = File.OpenRead("roger.bin");
            bf = new BinaryFormatter();
            roger = (Employee)bf.Deserialize(str);
            str.Close();
            Console.WriteLine(roger);
            
            // Serialize Roger specifying a file as the destination of the
            // serialized data. In this case, Roger's address is not included
            // in the serialized data.
            str = File.Create("roger.bin");
            bf = new BinaryFormatter();
            bf.Context = new StreamingContext(StreamingContextStates.File);
            bf.Serialize(str, roger);
            str.Close();
            // Deserialize and display Roger.
            str = File.OpenRead("roger.bin");
            bf = new BinaryFormatter();
            roger = (Employee)bf.Deserialize(str);
            str.Close();
            Console.WriteLine(roger);
            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Задача за час

Да се имплементира апликација за исцртување на топчиња во формата со фиксна големина (радиус 25) и одредена боја која се избира од ColorPickerDialog преку соодветно мени. Топчињата се исцртуваат со двоен лев клик во работната површина на формата.

Со помош на сериализација да се овозможи зачувување и вчитување на состојбата на програмата (исцртаните топчиња).

Clone this wiki locally