Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to Serialize Across Assemblies with the BinaryFormatter

0.00/5 (No votes)
18 May 2016 1  
Describes how to write your own SerializationBinder to allow serialization across assemblies

Introduction

I have fielded several questions now on how to serialize an object from one assembly and deserialize it into an equivalent object in another assembly.

Normally, you would use JSON or XML to achieve this but if you want/need to use the BinaryFormatter to create a much smaller, faster more efficient serialization, deserializing your object in a different assembly can be tricky.

The way to achieve this is to implement your own SerializationBinder. This is the object that is responsible for finding types during serialization and deserialization.

To implement your own binder, you derive a new class from the System.Runtime.Serialization.SerializationBinder class, and override the BindToType and BindToName methods:

/// <summary>
/// look up the type locally if the assembly-name is "NA"
/// </summary>
/// <param name="assemblyName"></param>
/// <param name="typeName"></param>
/// <returns></returns>
public override Type BindToType(string assemblyName, string typeName)
{
    if (assemblyName.Equals("NA"))
        return Type.GetType(typeName);
    else
        return defaultBinder.BindToType(assemblyName, typeName);
}

/// <summary>
/// override BindToName in order to strip the assembly name. Setting assembly name to null does nothing.
/// </summary>
/// <param name="serializedType"></param>
/// <param name="assemblyName"></param>
/// <param name="typeName"></param>
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
    // specify a neutral code for the assembly name to be recognized by the BindToType method.
    assemblyName = "NA";
    typeName     = serializedType.FullName;
}

The BindToName method is where the assembly-name and type-name are chosen to be written into the serialization. So your type can be de-serialized in another assembly, we are setting the assembly-name to "NA" - if you leave the assembly-name as NULL, the normal assembly name will be written into the stream, which is why we set a non-null value (you could use a zero-length string)

The BindToType method is essentially the reverse of the BindToName method. This is the method responsible for finding the correct type to de-serialize into.

Here, I am testing if the assembly-name is "NA", then performing a local search for the type-name using Type.GetType(string typeName).

To use the serialization binder, you simply set the Binder property of a new BinaryFormatter to an instance of your SerializationBinder. In the example, I have used a singleton for efficiency.

public static void Serialize<T>(this T graph, Stream target)
{
    // create the formatter:
    IFormatter formatter = new BinaryFormatter();
    
    // set the binder to the custom binder:
    formatter.Binder = TypeOnlyBinder.Default;
    
    // serialize the object into the stream:
    formatter.Serialize(target, graph);
}

public static T DeSerialize<T>(this Stream source)
{
    // create the formatter:
    IFormatter formatter = new BinaryFormatter();
    
    // set the binder to the custom binder:
    formatter.Binder = TypeOnlyBinder.Default;
    
    // serialize the object into the stream:
    return (T)formatter.Deserialize(source);
}

Using the Code

In the attached example solution, there are two console projects. Both define an object called "SimpleObject" in a namespace called "Serialization" - the two objects are identical, but are defined in separate assemblies.

[Serializable]
public class SimpleObject
{
    public string   Property { get; set; }
    public DateTime Date     { get; set; } = DateTime.Now;
}

One project creates a new instance of a SimpleObject, and serializes it into a file called "example.bin" in the solution directory:

static void Main(string[] args)
{
    // example code:
    var so = new SimpleObject { Property = "Test Property Value" };

    Console.WriteLine($"Simple Object Type: {so.GetType().AssemblyQualifiedName}");

    // serialize: go up sufficient directories that the file lands in the solution dir.
    using (var fs = File.OpenWrite("..\\..\\..\\example.bin"))
    {
        so.Serialize(fs);
    }
}

The other project "DeSerializeOtherAssembly" loads the example.bin file and deserializes it into that assembly's version of "SimpleObject":

static void Main(string[] args)
{
    using (var fs = File.OpenRead("..\\..\\..\\example.bin"))
    {
        var so = fs.DeSerialize<SimpleObject>();

        Console.WriteLine($"SimpleObject Type: {so.GetType().AssemblyQualifiedName}");
        Console.WriteLine($"SimpleObject.Property = {so.Property}");
        Console.WriteLine($"SimpleObject.Date = {so.Date}");
    }
}

NOTE

The code is written in C# 6.0, you might have a couple of syntax errors in any version of Visual Studio prior to 2015.

Points of Interest

The Serializer class used in the example is a pretty good helper object for binary serialization - it uses extension methods with generics for quick and easy strongly-typed serialization and deserialization.

History

  • @ Version 1.0

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here