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;
}
}
}
Mình sẽ khai báo BinaryFormater
để serialize object thành dạng binary như sau:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace demo
{
internal class Program
{
public static void BinaryFormatterSerialize(string file, object o)
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None);
bf.Serialize(fs,o);
fs.Close();
}
public static object BinaryFormatterDerialize(string file)
{
BinaryFormatter bf = new BinaryFormatter();
Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
object o = bf.Deserialize(stream);
stream.Close();
return o;
}
public static void Main(string[] args)
{
SerializeObject a = new SerializeObject("endy", 8);
string file = "data.bin";
BinaryFormatterSerialize(file, a);
SerializeObject deser = (SerializeObject)BinaryFormatterDerialize(file);
Console.WriteLine(deser.Name + deser.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ả:


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 BinaryFormatter
và SurrogateSelector
. 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