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 (but are not), 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 ask 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:

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: