Binary Serialization using BinaryFormatter in C#


In this article we will explain Binary Serialization using the BinaryFormatter type. We will also discuss about the customizations that can be done on the resulting stream during the Serialization process. Binary Serialization and Serialization in general are explained in Serialization article.

BinaryFormatter type defined in System.Runtime.Serialization.Formatters.Binary namespace serializes and deserializes an object or group of connected objects in object graph into binary format. Serialize method persists an object graph to specified stream as sequence of bytes and Deserialize method converts persisted sequence of bytes to an object graph.

All the classes in the inheritance hierarchy must be decorated with [Serializable] attribute, in order to serialize the derived class.

Serialize/Deserialize Code Example using BinaryFormatter

DerivedClass inherits from BaseClass. BaseClass has Name, id and ID members, DerivedClass has one member Age. In the Main function we create an instance of DervidedClass, change the Name property to John and then persist it to file “Sample.dat” using the Serialize method. You have to pass file stream and DerivedClass objects to the Serialize method. We then reopen the “Sample.dat” file and Deserialize the persisted stream to DerivedClass object using Deserialize method. Deserialize method takes stream as input and returns object type, you have to type cast and store in DerivedClass reference. “Sample.dat” file is created in the Bin directory of the application.

    [Serializable]
    public class BaseClass
    {
        public string Name = "Jack";

        private string id = "1234";

        public string ID
        {
            get
            {
                return this.id;
            } 
            set
            {
                this.id = value;
            }
        }
    }

    [Serializable]
    public class DerivedClass : BaseClass
    {
        public int Age = 22;
    }
    
    public static void Main()
    {
        // Create a DerivedClass object.
        DerivedClass derivedClassObject = new DerivedClass();

        // Change name from Jack to John
        derivedClassObject.Name = "John";

        // Create instance of BinaryFormatter.
        BinaryFormatter formatter = new BinaryFormatter();

        // Create a File stream with write access.
        using (Stream stream = new FileStream("Sample.dat", FileMode.Create, FileAccess.Write, FileShare.None))
        {
            // Serialize the derived class object by using the Serialize Method.
            formatter.Serialize(stream, derivedClassObject);
        }

        // Create an Instance of Derived Class object.
        DerivedClass derivedClassObjectDeserialzed = new DerivedClass();

        // Open the file created during serialization process.
        using (Stream stream = File.OpenRead("Sample.dat"))
        {
            // Deserialze the stream to DerivedClass object.
            derivedClassObjectDeserialzed = formatter.Deserialize(stream) as DerivedClass;
        }

        // Print properties to verify Deserialization process
        // is done properly. Name should be John.
        Console.WriteLine(derivedClassObjectDeserialzed.Name);
        Console.WriteLine(derivedClassObjectDeserialzed.ID);

        // OUTPUT
        // John
        // 1234
    }

Customizations to the persisted data can be done during the Serialization process. An example would be to encrypt sensitive information before persisting data or to store the data in a specific format. There are two approaches supported by BinaryFormatter to make customizations, we shall cover them below.

Customizing Serialization using ISerializable Interface

Classes decorated with [Serializable] attribute can also implement ISerializable interface to control the Serialization and Deserialization process. GetObjectData of the ISerializable interface is called during the serialization process. You should also define a private/protected constructor with SerializationInfo, which is used during the Deserialization process to rebuild the object.

In the below example, SecureData class is Serializable and implements ISerializable Interface. SecureField member is encrypted in the GetObjectData function before persisting and it is decrypted in the protected constrcutor which is called during deserialization process.

    [Serializable]
    public class SecureData : ISerializable
    {
        public string SecureField = "This is a secret";
        
        public SecureData()
        {
        }

        protected SecureData(SerializationInfo info, StreamingContext context)
        {
            this.SecureField = this.Decrypt(info.GetString("Secure"));
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Secure", this.Encrypt(SecureField));
        }

        private string Encrypt(string input)
        {
            // Encrypt the input here and return.
            return input;
        }

        private string Decrypt(string input)
        {
            // Decrypt the input here and return
            return input;
        }
    }

Customizing Serialization using Attributes

The preferred approach to customizing the Serialization process is by using the Serialization related attributes provided by .NET framework. It is much simpler than implementing ISerializable because you need to interact with SerializationInfo parameter to add and get values while using ISerializable method of customization.

The attributes [OnSerailizing], [OnSerialized], [OnDeserializing] and [OnDeserialized] can be used on methods with return type void and take StreamingContext as input parameter, the underlying framework calls these attributed methods. As the names suggests the methods attributed with one of above attributes are called at the appropriate time of the Serialize/Deserialize process.

In the below sample, SecureField is encrypted before serializing in the OnSerializing method, which is attributed with [OnSerializing]. SecureField is decrypted after deserializing in OnDeserialized method attributed with [OnDeserialized]

    [Serializable]
    public class SecureDataUsingAttributes
    {
        public string SecureField = "This is a secret";

        [OnSerializing]
        private void OnSerializing(StreamingContext context)
        {
            // This method is called before serializing process.
            // Encrypt the SecureField.
            this.SecureField = this.Encrypt(this.SecureField);
        }

        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            // This method is called after Deserialization Process.
            // Decrypt the SecureField.
            this.SecureField = this.Decrypt(this.SecureField);
        }

        private string Encrypt(string input)
        {
            // Encrypt the input here and return.
            return input;
        }

        private string Decrypt(string input)
        {
            // Decrypt the input here and return
            return input;
        }
    }