Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Binary Serialization with AnySerializer

5.00/5 (3 votes)
3 Dec 2018CPOL3 min read 10.5K  
How to binary serialize your classes without having to modify them

Introduction

Recently, I was tasked with having to serialize a large set of classes and detect changes to them at different points in time. This really involved two tasks: serialization and computing diffs on objects. Ordinarily, I would have jumped right into using ProtoBuf and started adding contract attributes to all of my classes. This would take some time to do, and ultimately would be a pain because of the number of classes and properties involved I would need to change. It was also made difficult because ProtoBuf doesn't like serializing interface types without defining all of the possible concrete classes that implement it. What I wanted was a way to just serialize the objects as-is, and not throw up if properties referenced interfaces or contained generic collections with interface references or circular references (Entity Framework data models for example).

Alternatives

I first tried using ProtoBuf and attributing all of my classes with ProtoContracts. This also required using ProtoInclude on any interfaces to make ProtoBuf aware of concrete classes that implement them. I also found myself having to exclude a lot of member fields that it didn't like (delegates, locks, etc.) and my code base was starting to get hairy.

After finally throwing my hands in the air with it, I thought I'd give Microsoft's BinaryFormatter a try even though it generates much larger data and is not nearly as performant. At least here, I'd only have to add [Serializable] attributes, right? Well, after going through several hundred classes and decorating with [NonSerializable] on fields and [IgnoreDataMember] on properties, it was starting to get hairy as well. I kind of had it working but it was painful and would blow up sometimes especially when I had to start modifying Entity Framework classes.

Using the Code

Getting started with AnySerializer is really, really simple. Install the nuget package into your project using Package Manager Console: Install-Package AnySerializer

Serialize any object of your choosing like this:

C#
using AnySerializer.Extensions;

var originalObject = new SomeComplexTypeWithDeepStructure();

// serialize to binary data
var bytes = originalObject.Serialize();

// restore the object from binary data
var restoredObject = bytes.Deserialize<SomeComplexTypeWithDeepStructure>();

And that is all that is needed to serialize your class!

Advanced Control

There may be cases where the plain usage doesn't work for your application. Such a use case would be interfaces with multiple concrete types. The serializer needs to be made aware which concrete type you would like to restore, but that doesn't mean you need to decorate it at compile time! Let's try another sample:

C#
using AnySerializer.Extensions;

var originalObject = new VehicleContainer(new Car 
{ Color = "Red", Make = "Dodge", Model = "Challenger", DoorCount = 4});

// serialize to binary data
var bytes = originalObject.Serialize(SerializerOptions.EmbedTypes);

// restore the object from binary data
var restoredObject = bytes.Deserialize<VehicleContainer>(originalObject.GetType());

Assert.AreEqual(typeof(Car), restoredObject.Vehicle);

public class VehicleContainer
{
  public IVehicle Vehicle { get; set; }
  public VehicleContainer(IVehicle vehicle)
  {
    Vehicle = vehicle;
  }
}

public interface IVehicle
{
  string Color { get; }
  string Make { get; }
  string Model { get; }
}

public class Car : IVehicle
{
  string Color { get; }
  string Make { get; }
  string Model { get; }
  int DoorCount { get; }
}

public class Truck : IVehicle
{
  string Color { get; }
  string Make { get; }
  string Model { get; }
  string Is4x4 { get; } 
}

See the difference? We need to pass SerializerOptions to tell the Serializer to embed type information for any types that require it. This will include information only about types that need concrete type information in order to restore.

There are lots of other scenarios that can be at times challenging to serialize. Here are a few examples!

Serialize anonymous types:

C#
using AnySerializer.Extensions;

var originalObject = new { Id = 1, Name = "John Doe" };

// serialize to binary data
var bytes = originalObject.Serialize();

// restore the object from binary data
var restoredObject = bytes.Deserialize(originalObject.GetType());

Serialize any collection type (IEnumerable, ICollection, List, Dictionary, Hashtable, etc.):

C#
using AnySerializer.Extensions;

var colorsList = new List<string> { "Red", "Green", "Blue" };
var foodsDictionary = new Dictionary<string, string> 
{ { "Tomato", "Red" }, { "Banana", "Yellow" } };

// serialize to binary data
var colorsBytes = colorsList.Serialize();
var foodsDictionary = colorsList.Serialize();

// restore the object from binary data
var restoredColorsList = bytes.Deserialize<List<string>>();
var restoredFoodsDictionary = bytes.Deserialize<Dictionary<string,string>>();

Serialize tuples:

C#
using AnySerializer.Extensions;

var originalObject = new Tuple<int, string>(1, "John Doe");
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<Tuple<int,string>>();

// or use C# 7 ValueTuples
var originalObject = (1, "John Doe");
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<(int, string)>();

// C# 7 named Tuples
var originalObject = (id: 1, name: "John Doe");
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<(int, string)>();

Constructorless or constructors with parameters object:

C#
using AnySerializer.Extensions;

var originalObject = ConstructorlessObject.Create();
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<ConstructorlessObject>();

public class ConstructorlessObject
{
  private ConstructorlessObject() { }
  public static ConstructorlessObject Create()
  {
    return new ConstructorlessObject();
  }
}

var originalObject = new ConstructorWithParametersObject(1, "John Doe");
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<ConstructorlessObject>();

public class ConstructorWithParametersObject
{
  public ConstructorlessObject(int id, string name) { }
}

Custom collections:

C#
using AnySerializer.Extensions;

var originalObject = new CustomCollectionObject(1, 2);
originalObject.Add("test1", 1);
originalObject.Add("test2", 2);
originalObject.Add("test3", 3);
var bytes = originalObject.Serialize();
var restoredObject = bytes.Deserialize<CustomCollectionObject>();

public class CustomCollectionObject : IEquatable<CustomCollectionObject>
{
  private readonly Dictionary<string, int> _innerStorage = new Dictionary<string, int>();
  private int _a;
  private int _z;
  public ICollection<string> Keys
  {
    get { return _innerStorage.Keys; }
  }

  public ICollection<int> Values
  {
    get { return _innerStorage.Values; }
  }

  public void Add(string key, int value)
  {
    if (!_innerStorage.ContainsKey(key))
    {
       _innerStorage.Add(key, value);
    }
  }

  public int this[string key]
  {
    get
    {
      if (!_innerStorage.TryGetValue(key, out int item))
      {
        _innerStorage.Add(key, item);
      }
      return item;
    }
    set
    {
       _innerStorage[key] = value;
    }
  }

  public CustomCollectionObject(int a, int z)
  {
    _a = a;
    _z = z;
  }
}

How Does It Stack Up Against Others?

Feature AnySerializer ProtoBuf BinaryFormatter ZeroFormatter
Declarative-Free X      
Circular-References X X    
Interfaces X x    
Constructorless X x    
.Net Core X X X X
.Net Framework X X X X
Compression X      
Graph support X x X X
Anonymous X      
Private X X X X
Ignore Property X      
Ignore Field X      
Size 59b 32b 414b 414b
Speed* Ser/Deser 3 / 2 2 / 1 10 / 8 1 / 2

X - feature supported
x - indicates declarative support for feature
* - Speed references (Serialize / Deserialize) are ranked from 1 - 10, lower is better.

There are many scenarios that are difficult to do with other serializers that are made simple with AnySerializer. Check out the descriptive Wiki for details on how to accomplish almost anything!

History

  • 3rd December, 2018: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)