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à:
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
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 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