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:
public override Type BindToType(string assemblyName, string typeName)
{
if (assemblyName.Equals("NA"))
return Type.GetType(typeName);
else
return defaultBinder.BindToType(assemblyName, typeName);
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
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)
{
IFormatter formatter = new BinaryFormatter();
formatter.Binder = TypeOnlyBinder.Default;
formatter.Serialize(target, graph);
}
public static T DeSerialize<T>(this Stream source)
{
IFormatter formatter = new BinaryFormatter();
formatter.Binder = TypeOnlyBinder.Default;
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)
{
var so = new SimpleObject { Property = "Test Property Value" };
Console.WriteLine($"Simple Object Type: {so.GetType().AssemblyQualifiedName}");
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