One of the great features of this network library is the ability to send custom objects and at the same time easily incorporate compression, encryption etc. This is in part due to the features made available from serialisation libraries such as protobuf-net. Demonstrating this functionality is the primary goal of the AdvancedSend example included in the ExampleConsole project of the download bundle. The goal of this tutorial is to briefly discuss some of the methods available when using more complex custom objects. There are several different approaches to serialising and deserialising more complex objects so if this tutorial does not cover your scenario please leave a comment or post on our forum.

As a short recap, most custom objects can be sent using the following syntax:

CustomObject myCustomObject = new CustomObject("ben", 25);
NetworkComms.SendObject("Message", "127.0.0.1", 10000, myCustomObject);

If using the Protobuf serialiser the class CustomObject should be defined as follows:
[ProtoContract]
private class CustomObject
{
    [ProtoMember(1)]
    public int Age { get; private set; }

    [ProtoMember(2)]
    public string Name { get; private set; }

    /// <summary>
    /// Parameterless constructor required for protobuf
    /// </summary>
    protected CustomObject() { }

    public CustomObject(string name, int age)
    {
        this.Name = name;
        this.Age = age;
    }
}

Note: Notice the addition of [ProtoContract] and [ProtoMember(n)] attributes.

This works very well for objects which contain value types and / or collections (e.g. list, dictionary etc)  of primitive data types. You can also use members that are instances of custom classes (or collections of them) that have also been marked up for serialization in this way.  Things can get a little bit more interesting if you want to include types that have not been designed for serialization using protobuf-net. A good example of this is an Image object. Although you can still use these in your custom objects you need to provide some extra implementation to ensure they can be serialised and deserialised correctly.

Non-Primitive Data Types

The following object acts as a wrapper for an Image object, note the addition of two methods

Serialize()

and
Deserialize()

which respectively have the attributes
[ProtoBeforeSerialization]

and
[ProtoAfterDeserialization]

. Since it is not possible to serialise the Image object directly these methods convert it into its binary form, byte[], on serialisation and deserialisation and store the result privately.
[ProtoContract]
public class ImageWrapper
{
    /// <summary>
    /// Private store of the image data as a byte[]
    /// This will be populated automatically when the object is serialised
    /// </summary>
    [ProtoMember(1)]
    private byte[] _imageData;

    /// <summary>
    /// The image name
    /// </summary>
    [ProtoMember(2)]
    public string ImageName { get; set; }

    /// <summary>
    /// The public accessor for the image. This will be populated
    /// automatically when the object is deserialised.
    /// </summary>
    public Image Image { get; set; }

    /// <summary>
    /// Private parameterless constructor required for deserialisation
    /// </summary>
    private ImageWrapper() { }

    /// <summary>
    /// Create a new ImageWrapper
    /// </summary>
    /// <param name="imageName"></param>
    /// <param name="image"></param>
    public ImageWrapper(string imageName, Image image)
    {
        this.ImageName = imageName;
        this.Image = image;
    }

    /// <summary>
    /// Before serialising this object convert the image into binary data
    /// </summary>
    [ProtoBeforeSerialization]
    private void Serialize()
    {
        if (Image != null)
        {
            //We need to decide how to convert our image to its raw binary form here
            using (MemoryStream inputStream = new MemoryStream())
            {
                //For basic image types the features are part of the .net framework
                Image.Save(inputStream, Image.RawFormat);

                //If we wanted to include additional data processing here
                //such as compression, encryption etc we can still use the features provided by NetworkComms.Net
                //e.g. see DPSManager.GetDataProcessor<LZMACompressor>()

                //Store the binary image data as bytes[]
                _imageData = inputStream.ToArray();
            }
        }
    }

    /// <summary>
    /// When deserialising the object convert the binary data back into an image object
    /// </summary>
    [ProtoAfterDeserialization]
    private void Deserialize()
    {
        MemoryStream ms = new MemoryStream(_imageData);

        //If we added custom data processes we have the perform the reverse operations here before 
        //trying to recreate the image object
        //e.g. DPSManager.GetDataProcessor<LZMACompressor>()

        Image = Image.FromStream(ms);
        _imageData = null;
    }
}

This approach can be used to successful include any non-primitive types in your custom objects. If you found this tutorial helpful, have any feedback or questions please leave a comment or post on our forums.

Inherited Classes

It is also possible to use inheritance structures. We can reuse the CustomObject class originally defined above, we only need to add an additional attribute to inform protobuf-net of the child class and also allow access to the parameterless constructor by changing it from private to protected:

[ProtoContract]
//As with the ProtoMembers we include child classes in the 
//serialisation format using the ProtoInclude attribute
//The (tag) 100 on the ProtoInclude can be anything as long as it 
//does not clash with the ProtoMember attribute tags.
[ProtoInclude(100, typeof(CustomObjectExt))]
class CustomObject
{
    [ProtoMember(1)]
    public int Age { get; private set; }

    [ProtoMember(2)]
    public string Name { get; private set; }

    /// <summary>
    /// Parameterless constructor required for protobuf
    /// </summary>
    protected CustomObject() { }

    public CustomObject(string name, int age)
    {
        this.Name = name;
        this.Age = age;
    }
}

And the child class which uses CustomObject as a base:
//CustomObjectExt extends CustomObject
[ProtoContract]
class CustomObjectExt : CustomObject
{
    [ProtoMember(1)]
    public double Height { get; private set; }

    /// <summary>
    /// Parameterless constructor required for protobuf
    /// </summary>
    private CustomObjectExt() 
        : base () { }

    public CustomObjectExt(string name, int age, double height)
        : base (name, age)
    {
        this.Height = height;
    }
}

If an instance of the class ‘CustomObjectExt’ is serialized it will also retain any data from the base class.

Streams

You can also send directly using streams, you just nee to wrap them in StreamSendWrapper (API Link) first.

For More Information

  • Please see the protobuf-net documentation for further examples of the features available for serialisation and deserialisation.