Tuesday, 16 May 2017

Serialization 01 binary

Serialization and de-serialization are the processes of taking your object and transforming it into a state that can be retained in a file. Now you most definitely could create a function that creates a string representation of your class that you could then write to a file using a stream, and then create another function that would then transform that data into instances of your class, but that's basically serialization. So use what's provided for you instead, Binary Serialization

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Person(string FirstName, string LastName) {
        this.FirstName = FirstName;
        this.LastName = LastName;
    }
    public string FullName { get { return $"{FirstName} {LastName}"; } }
}

class Employee : Person {
    static int _runningId;
    public int Id { get; private set; }
    public Employee(string FirstName, string LastName) : base(FirstName,LastName) { }
}

Above we created two related classes, now let's make them "Serializable"

[Serializable]
class Person
{
    //...
}

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


and that's it all we did was added a [Serializable] attribute to our class and we can now serialize our class. Next lets use the BinaryFormater to serialize an array of our classes.

class Program
{
    static void Main(string[] args)
    {
        var ppl = new Person[] {
            new Person("John", "Smith"), new Person("Jane","Doe"),
            new Employee("Sally","Johnson"),new Person("Alejandro", "Cruz"),
            new Person("Diago","Pendaz"), new Employee("Tim","Chan")};

        var formatter = new BinaryFormatter();

        using (var fs = new FileStream(@"c:\ppl.data", FileMode.Create, FileAccess.Write))
            formatter.Serialize(fs, ppl);

        Person[] People;

        using (var fs = new FileStream(@"c:\ppl.data", FileMode.Open, FileAccess.Read))
                People = formatter.Deserialize(fs) as Person[];

        foreach (var p in People)
            Console.WriteLine(p.FullName);
    }

}

and that's it easy as that. Now there's a couple of caveats when working with the Binary Serializer
  • Constructors do not fire, no constructor fires when deserializing, you just get an instance of the object you serialized with the properties that where serialized.
  • If you don't want a property serialized you can use the [NonSerialized] field attribute.
  • By default the serializer BinaryFormat is set to simple, if you set it to full, then deserializing a null property supposedly is going to throw an exception, unless that property is marked with the [OptionalField] field attribute; however that is not what I've experienced.
  • All the fields contained in your serializable object must also be serializable
We can also add methods that will fire throughout the serialization and deserialization processes 

[Serializable]
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Person(string FirstName, string LastName)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
    }
    public string FullName
    {
        get
        { return $"{FirstName} {LastName}"; }
    }

    [OnSerializing()]
    internal void OnSerializingMethod(StreamingContext ctx)
    {
        Console.WriteLine("OnSerializing.");
    }
    [OnSerialized()]
    internal void OnSerializedMethod(StreamingContext ctx)
    {
        Console.WriteLine("OnSerialized.");
    }
    [OnDeserializing()]
    internal void OnDeserializingMethod(StreamingContext ctx)
    {
        Console.WriteLine("OnDeserializing.");
    }
    [OnDeserialized()]
    internal void OnDeserializedMethod(StreamingContext ctx)
    {
        Console.WriteLine("OnDeserialized.");
    }
}

these methods are called from the the BinaryFormater so there's no need to explicitly call them, they give you a nice place to manipulate data as it's being serialized or after it's serialized.