Containers

A container is a class or structure you create that will be serialized in place of another type (structure or class) that would not serialize or would be problematic (not efficient, missing fields, etc.).
It inherits the interface ITypeContainer.

Making a ITypeContainer

The objective is to replace the problematic instance by an easy instance. In general, the container will contain very simple types (string, int, byte array, etc.).

Let's take an example:

/// <summary>
/// . There is no default (no-param) constructor.
/// . The only constructor has a parameter with no corresponding field.
/// . ATextBox has a private 'set' and is different type from constructor's parameter.
/// </summary>
public class MyStrangeClassNeedsACustomContainer
{
	/// <summary>
	/// It is built from the constructor's parameter.
	/// Since its 'set' method is not public, it will not be serialized directly.
	/// </summary>
	public TextBox ATextBox { get; private set; }

	public MyStrangeClassNeedsACustomContainer(int NumberAsTitle)
	{
		this.ATextBox = new TextBox() { Text = NumberAsTitle.ToString() };
	}
}

As written in the summary, this class causes some difficulties to the serializer(s).

To overcome the problem, we create a container:

class ContainerForMyStrangeClass : ITypeContainer
{
	#region Here you add data to be serialized in place of the class instance

	public int AnInteger; // We store the smallest, sufficient and necessary data.

	#endregion Here you add data to be serialized in place of the class instance


	public ITypeContainer CreateNewContainer(object ContainedObject)
	{
		MyStrangeClassNeedsACustomContainer sourceInstance = ContainedObject as MyStrangeClassNeedsACustomContainer;
		return new ContainerForMyStrangeClass() { AnInteger = int.Parse(sourceInstance.ATextBox.Text) };
	}

	public object Deserialize()
	{
		return new MyStrangeClassNeedsACustomContainer(this.AnInteger);
	}

	public bool IsValidType(Type type)
	{
		return Tools.TypeIs(type, typeof(MyStrangeClassNeedsACustomContainer));
	}

	public bool ApplyEvenIfThereIsAValidConstructor
	{
		get { return false; }
	}

	public bool ApplyToStructures
	{
		get { return false; }
	}
}

A detail: all methods behave as static methods (although they are instance methods).
Except Deserialize().

Please note a container class can have two roles:

  1. As a container constructor.
    This role comes from the interface IContainerGenerator.
    One, and only one, instance of the container is instantiated for this role.
    The serializer asks this instance if this type of container can encapsulate a value, by calling functions as IsValidType(Type type).
    If the answer is yes, the serializer commands the container to produce another instance that will contain the value.
  2. As an actual container.
    This role comes from the interface IContainer.
    Every instance of this container contains a representation if the original value.
    The deserializer

ITypeContainer inherits both the two interfaces (and consequently the two roles too) :
public interface ITypeContainer : IContainerGenerator, IContainer { }

 

Let's see them more in details:

  • public int AnInteger
    
    It is not part of the ITypeContainer interface. In this example here we store the information we need later at deserialization.
  • ITypeContainer CreateNewContainer(object ContainedObject)
    
    Called on serialization. This is a kind of constructor for this container instance. The parameter will be the source class instance to serialize.
  • object Deserialize()
    

    Called on deserialization. The container instance will produce a new instance, a copy of the source instance, using our field AnInteger.

  • bool IsValidType(Type type)
    
    Called on serialization. Returns true is the type can be contained in this container. This is a filter. We can choose to accept inherited types or not, to accept several compatible types, etc..
  • bool ApplyEvenIfThereIsAValidConstructor
    
    Called on serialization. Returns true if this container applies to class types with a default (no-param) constructor. Can be useful to very general containers.
  • bool ApplyToStructures
    
    Called on serialization. Returns true if this container applies to structure types, and not only class types. Can be useful to very general containers.

Steps are:

  1. The serializer checks if the source type (MyStrangeClassNeedsACustomerContainer) is managed by a container. Our container class (ContainerForMyStrangeClass) answers yes, via IsValidType(), ApplyEvenIfThereIsANoParamConstructor and ApplyToStructures.
  2. The serializer builds an instance of our container, via CreateNewContainer(). CreateNewContainer builds an instance and sets its field AnInteger.
  3. The serializer stores (serializes) this container instance in place of the source instance.
  4. The deserializer retrieves (deserializes) the container instance.
  5. The deserializer calls Deserialize() and obtains a copy of the source class instance. Deserialize() creates this copy using its field AnInteger.

 We declare the CustomModifiers globally :

public class CustomContainerTestModifiers : CustomModifiers
{
	public CustomContainerTestModifiers()
		: base(Containers: new ITypeContainer[] {
			new ContainerForMyStrangeClass() // The constructor role instance.
		})
	{
	}
}

This declaration will automatically be found by the deserializer.

Now we serialize it:

/* This example needs a custom ITypeContainer.
Normally, this class can not be serialized (see details in its source).
But thanks to this container, we can serialize the class as a small data (an integer).
 */

var data = new MyStrangeClassNeedsACustomContainer(123);

using (MemoryStream ms = new MemoryStream())
{
	UniversalSerializer ser = new UniversalSerializer(ms);

	ser.Serialize(data);
	var data2 = ser.Deserialize<MyStrangeClassNeedsACustomContainer>();

	bool ok = data2.ATextBox.Text == "123";
}

The right container is automatically found by UniversalSerializer, thanks to the global declaration.

As you can see, the implementation is very easy.

Tool help functions

The static class Tools offers some help:

  • Type Tools.TypeIs(Type ObjectType, Type SearchedType)
    
    It is equivalent to the C#'s 'is', but for Types.
    For example, TypeIs((typeof(List<int>), typeof(List<>)) returns true.
  • Type Tools.DerivedType(Type ObjectType, Type SearchedType)
    

    Returns the type corresponding to SearchedType that is inherited by ObjectType.
    For example, DerivedType(typeof(MyList), typeof(List<>)) returns typeof(List<int>) when MyList is
    MyList: List<int> { }

  • FieldInfo Tools.FieldInfoFromName(Type t, string name)
    
    Returns the FieldInfo of the named field of the type.

Please share your containers

If you wrote a container for a common type, please share it.
I will be more than happy to include it in the serializer.