IBinarySerializable Interface

Makes possible quick and compact custom serialization of a class by BinarySerializer and BinarySerializationFormatter.

Definition

Namespace: KGySoft.Serialization.Binary
Assembly: KGySoft.CoreLibraries (in KGySoft.CoreLibraries.dll) Version: 8.1.0
C#
public interface IBinarySerializable

Remarks

By this interface a class can be serialized into a compact byte array. Unlike in case of system serialization and ISerializable implementations, saved data does not have to contain a name based mapping. Thus the saved content can be more compact but this solution can be discouraged on very large object graphs because the whole object has to be written into memory. In case of very large object hierarchies you might consider to implement ISerializable interface instead, which is also supported by BinarySerializer and BinarySerializationFormatter.

If the implementer has a constructor with (BinarySerializationOptions, byte[]) parameters, then it will be called for deserialization. This makes possible to initialize read-only fields. If no such constructor exists, then the Deserialize method will be called. If the implementer has a parameterless constructor, then it will be called before calling the Deserialize method; otherwise, no constructor will be used at all.

Methods decorated by OnSerializingAttribute, OnSerializedAttribute, OnDeserializingAttribute and OnDeserializedAttribute as well as calling IDeserializationCallback.OnDeserialization method of implementers are fully supported also for IBinarySerializable implementers. Attributes should be used on methods that have a single StreamingContext parameter.

Example

Following example demonstrates the usage of the special constructor version.
C#
using System;
using System.IO;
using KGySoft.Serialization.Binary;

// This is a simple sealed class that will never be derived
public sealed class ExampleSimple : IBinarySerializable
{
    public int IntProp { get; }
    public string StringProp { get; }

    // this is the ordinary constructor
    public ExampleSimple(int intValue, string stringValue)
    {
        IntProp = intValue;
        StringProp = stringValue;
    }

    // this is the special constructor used by the deserializer
    // if you have read-only fields you must implement this constructor
    private ExampleSimple(BinarySerializationOptions options, byte[] serData)
    {
        using (BinaryReader reader = new BinaryReader(new MemoryStream(serData)))
        {
            IntProp = reader.ReadInt32();
            bool isStringPropNull = reader.ReadBoolean();
            StringProp = isStringPropNull ? null : reader.ReadString();
        }
    }

    public byte[] Serialize(BinarySerializationOptions options)
    {
        MemoryStream ms = new MemoryStream();
        using (BinaryWriter writer = new BinaryWriter(ms))
        {
            writer.Write(IntProp);
            writer.Write(StringProp == null);
            if (StringProp != null)
                writer.Write(StringProp);
        }
        return ms.ToArray();
    }

    public void Deserialize(BinarySerializationOptions options, byte[] serData)
    {
        throw new InvalidOperationException("Will not be called because special constructor is implemented");
    }
}
The following example introduces a pattern that can be used for serialization and deserialization serializable base and derived classes and with versioned content (optional fields):
C#
using System.IO;
using KGySoft.Serialization.Binary;

public class SerializableBase : IBinarySerializable
{
    public int IntProp { get; set; }
    public string StringProp { get; set; }

    private static int currentVersionBase = 1;

    // this is the ordinary constructor
    public SerializableBase(int intValue, string stringValue)
    {
        IntProp = intValue;
        StringProp = stringValue;
    }

    // parameterless constructor: will be called on deserialization
    // if exists and there is no special constructor
    protected SerializableBase()
    {
    }

    byte[] IBinarySerializable.Serialize(BinarySerializationOptions options)
    {
        MemoryStream ms = new MemoryStream();
        using (BinaryWriter writer = new BinaryWriter(ms))
        {
            SerializeContent(writer);
        }
        return ms.ToArray();
    }

    void IBinarySerializable.Deserialize(BinarySerializationOptions options, byte[] serData)
    {
        using (BinaryReader reader = new BinaryReader(new MemoryStream(serData)))
        {
            DeserializeContent(reader);
        }
    }

    protected virtual void SerializeContent(BinaryWriter writer)
    {
        writer.Write(currentVersionBase);
        writer.Write(IntProp);
        writer.Write(StringProp == null);
        if (StringProp != null)
            writer.Write(StringProp);
    }

    protected virtual void DeserializeContent(BinaryReader reader)
    {
        int version = reader.ReadInt32();
        IntProp = reader.ReadInt32();
        bool isStringPropNull = reader.ReadBoolean();
        StringProp = isStringPropNull ? null : reader.ReadString();
        // TODO: Read rest if version changes
    }
}

public class SerializableDerived: SerializableBase
{
    public bool BoolProp { get; set; }

    // This property is new in this class (optional content)
    public int NewIntProp { get; set; }

    private static int currentVersionDerived = 2;

    // this is the ordinary constructor
    public SerializableDerived(int intValue, string stringValue, bool boolValue)
        : base(intValue, stringValue)
    {
        BoolProp = boolValue;
    }

    // parameterless constructor: will be called on deserialization
    // if exists and there is no special constructor
    protected SerializableDerived()
        : base()
    {
    }

    protected override void SerializeContent(BinaryWriter writer)
    {
        base.SerializeContent(writer);
        writer.Write(currentVersionDerived);
        writer.Write(BoolProp);
        writer.Write(NewIntProp);
    }

    protected override void DeserializeContent(BinaryReader reader)
    {
        base.DeserializeContent(reader);
        int version = reader.ReadInt32();
        BoolProp = reader.ReadBoolean();
        if (version < 2)
            return;
        NewIntProp = reader.ReadInt32();
    }
}

  Notes to Implementers

Of course the special constructor way can be used here, too. Derived constructors should just call the base constructor, which should call DeserializeContent. In that case FxCop and ReSharper may emit a warning that virtual method is called from a constructor but that is alright here because this is a clean initialization pattern.

Methods

Deserialize Deserializes the inner state of the object from a byte array. Called only when the implementer does not have a constructor with (BinarySerializationOptions, byte[]) parameters. Without such constructor parameterless constructor will be called if any (otherwise, no constructors will be executed). The special constructor should be used if the class has read-only fields to be restored.
Serialize Serializes the object into a byte array.

See Also