Làm quen với .NET Serialization

Trong .net hỗ trợ nhiều cách khác nhau để serialize dữ liệu, tất cả đều có chung mục đích là serialize object, nhưng điểm khác biệt là format output sẽ khác nhau. Các hàm dùng để serialize object trong .net có thể được gọi là formatter hoặc serializer và có thể phân loại theo 3 prefix là:

  • Serializer như: XmlSerializer, DataContractSerializer, JavaScriptSerializer

  • Formatter như: BinaryFormatter, SoapFormatter

  • Reader như: XamlReader

  • Hoặc thậm chí là từ thư viện thứ ba như JSON.NET

Ta sẽ bắt đầu với gia phả nhà Formatter để làm quen với quá trình Serialize trong .net

Formatter

Đối với formatter thì có 3 loại phổ biến:

  • BinaryFormatter:cho ra kết quả là binary

  • SoapFormatter:cho ra kết quả dưới dạng soap format

  • ObjectStateFormatter:dùng với ViewState

Để một object có thể serialize, ta chỉ cần thêm attribute [Serializable] (không giống như Java phải implement lại Serializable interface)

Ví dụ một object có thể serialize

using System;

namespace demo
{
    [Serializable]
    public class SerializeObject
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public SerializeObject(string name, int age)
        {
            this.Name = name;
            this.Age = age;
        }
    }
}

Khi deser ta sẽ lưu kết quả vào file data.bin , ta có thể thấy dữ liệu được lưu vào file data.bin là dạng binary

Còn khi deser ta sẽ có kết quả

Magic Methods

Trong quá trình serialize và deserialze đương nhiên sẽ có những magic method được gọi, đối với .net thì những magic method sẽ được gọi là callback method trong quá trình seri/deser

Cụ thể quá trình seri/deser diễn ra như thế nào thì có thể tham khảo: dotnet-serialize-101.mdY4er/dotnet-deserialization

Trong .NET một Object Serialize sẽ có 4 magic method cơ bản như sau:

  • OnSerializing: được gọi trước khi quá trình Serialize diễn ra

  • OnSerialized: được gọi khi hoàn thành quá trình Serialize

  • OnDeserializing: được gọi trước khi quá trình Deserialize diễn ra

  • OnDeserialized: được gọi khi hoàn thành quá trình Deserialize

Code ví dụ:

    using System;
    using System.Runtime.Serialization;

    namespace demo
    {
        [Serializable]
        public class SerializeObject
        {
            public string Name { get; set; }
            public int Age { get; set; }

            public SerializeObject(string name, int age)
            {
                this.Name = name;
                this.Age = age;
            }
            [OnDeserializing]
            private void TestOnDeserializing(StreamingContext sc) { Console.WriteLine("OnDeserializing"); }
            
            [OnDeserialized]
            private void TestOnDeserialized(StreamingContext sc) { Console.WriteLine("OnDeserialized"); }
            
            [OnSerializing]
            private void TestOnSerializing(StreamingContext sc) { Console.WriteLine("OnSerializing"); }
            
            [OnSerialized]
            private void TestOnSerialized(StreamingContext sc) { Console.WriteLine("OnSerialized"); }
        }
    }

Khi seri và deser Object trên ta được kết quả

Ngoài 4 methods trên thì nếu khi ta implement lại một số interface có hỗ trợ serialize ta sẽ có thêm nhiều magic methods khác, ví dụ như khi implement ISerializable interfaces thì ta phải implement lại constructor và method GetObjectData , và method này sẽ được gọi trong quá trình serialize

Code ví dụ:

    using System;
    using System.Runtime.Serialization;

    namespace demo
    {
        [Serializable]
        public class SerializeObject : ISerializable
        {
            public string Name { get; set; }
            public int Age { get; set; }

            public SerializeObject(string name, int age)
            {
                this.Name = name;
                this.Age = age;
            }
            [OnDeserializing]
            private void TestOnDeserializing(StreamingContext sc) { Console.WriteLine("OnDeserializing"); }
            
            [OnDeserialized]
            private void TestOnDeserialized(StreamingContext sc) { Console.WriteLine("OnDeserialized"); }
            
            [OnSerializing]
            private void TestOnSerializing(StreamingContext sc) { Console.WriteLine("OnSerializing"); }
            
            [OnSerialized]
            private void TestOnSerialized(StreamingContext sc) { Console.WriteLine("OnSerialized"); }

            public void GetObjectData(SerializationInfo info, StreamingContext context)
            {
                Console.WriteLine("GetObjectData is called");
            }
            protected SerializeObject(SerializationInfo info, StreamingContext context)
            {
                Console.WriteLine("CustomObject2 constructor is called");
            }

        }
    }

Kết quả:

Lưu ý: ta có thể thấy khi implement 2 methods trên ta cần phải khai báo kèm theo SerializationInfo class, nguyên nhân của việc này là .net cho phép ta có thể can thiệp vào quá trình seri hoặc deser thông qua SerializationInfo. SerializationInfo giống như một biến đại diện cho thông tin của seri stream, mọi thông tin như type, giá trị của object trong quá trình seri/deser đều được lưu trong này

Tóm tắt:

Ngoài ra thằng Formarter còn có tính năng cho phép ta serialize object có type không hỗ trợ. Ta có thể làm điều đó với interface ISerializationSurrogate . Ta có thể hình dung Interface này giống như một “người đại diện” giúp ta có thể serialize được object type mà Formarter không hỗ trợ. Ví dụ mình có object Person, là một object bình thường không sử dụng attribute [Serialize]

namespace demo
{
    class Person
    {
        public string Name { get; set; }
        public Person(string name)
        {
            Name = name;
        }
        public override string ToString()
        {
            return Name;
        }
        
    }
}

Đây hoàn toàn là object type mà Formatter không thể serialize, do đó ta sẽ khai báo thêm một class để làm “đại diện” cho Class Person này khi thực hiện serialize

using System;
using System.Runtime.Serialization;

namespace demo
{
    class PersonSerializeSurrogate : ISerializationSurrogate
    {
        public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context)
        {
            Console.WriteLine("ISerializationSurrogate.GetObjectData is calling");
            var p = (Person)obj;
            info.AddValue("Name", p.Name);
        }

        public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            Console.WriteLine("ISerializationSurrogate.SetObjectData is calling");
            var p = (Person)obj;
            p.Name = info.GetString("Name");
            return p;
        }
    }
}

Khi implement ISerializationSurrogate ta sẽ phải khai báo 2 method là:

  • GetObjectData: được gọi khi serialize

  • SetObjectData: được gọi khi deser

Để Formarter có thể sử dụng được ISerializationSurrogate ta tiến hành khai báo tại hàm main như sau:

var formatter = new BinaryFormatter(); 
SurrogateSelector ss = new SurrogateSelector();
ss.AddSurrogate(typeof(Person), formatter.Context, new PersonSerializeSurrogate());
formatter.SurrogateSelector = ss; 
FileStream fs = new FileStream("data.bin", FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(fs, new Person("endy"));
fs.Close();

Mình sẽ khai báo 2 instance của BinaryFormatterSurrogateSelector. Sau đó dùng method AddSurrogate để khai báo object type nào sẽ được PersonSerializeSurrogate làm “đại diện”, ở đây mình sẽ có type của Person

Chi tiết các agrument của method AddSurrogate

Tiếp theo mình add “đại diện” vào Formatter và tiến hành serialize như bình thường, mình ghi kết quả ra file để so sánh kết quả khi dùng ISerializationSurrogate và khi không dùng

Các bạn có thể thấy object được serialize thông qua ISerializationSurrogate không khác gì một object được serialize trực tiếp như bình thường

Về thứ thự gọi callback methods trong việc serialize thông qua “đại diện” thì GetObjectData sẽ nằm giữa OnSerializing và OnSerialized, còn SetObjectData thì nằm giữa OnDeserializing và OnDeserialized

Tóm tắt quá trình seri của Formatter sẽ như sau

Last updated