What’s Bond?
The Microsoft github repos. for Bond states that “Bond is an open source, cross-platform framework for working with schematized data. It supports cross-language serialization/deserialization and powerful generic mechanisms for efficiently manipulating data.”
To put it another way, Bond appears to be similar to Google’s protocol buffers. See Why Bond? for more on what Bond is.
At the time of writing, out of the box, Bond is supported with C++. C# and Python language bindings.
Jumping straight in
Let’s jump straight in an write some code. Here’s the steps to create our project
- For this example, let’s create a Console project in Visual Studio
- Using NuGet add the Bond.CSharp package
Now we’ll define our schema using Bond’s IDL. This should be saved with the .bond extension, so in my case, this code is in Person.bond
namespace Sample struct Person { 0: string FirstName; 1: string LastName; 2: int32 Age; }
Notice that we create a namespace and then use a Bond struct to define our data. Within the struct, each item of data is preceded with an numeric id (see IDL Syntax for more information).
Now, we could obviously write the code to represent this IDL, but it’s be better still if we can generate the source code from the IDL. When we added the NuGet Bond.CSharp package we also got a copy of gbc, which is the command line tool for this purpose.
Open up a cmd prompt and locate gbc (mine was installed into
gbc c# <Project>\Person.bond -o=<Project>
Replace <Project> with the file path of your application and where your .bond file is located..
This command will generate the source files from the Person.bond IDL and output (the -o switch) to the root of the project location.
Now we need to include the generated files in our project – mine now includes Person_interfaces.cs, Person_proxies.cs, Person_services.cs and Person_types.cs. In fact we only need the Person_types.cs for this example. This includes the C# representation of our IDL and looks (basically) like this
public partial class Person { [global::Bond.Id(0)] public string FirstName { get; set; } [global::Bond.Id(1)] public string LastName { get; set; } [global::Bond.Id(2)] public int Age { get; set; } public Person() : this("Sample.Person", "Person") {} protected Person(string fullName, string name) { FirstName = ""; LastName = ""; } }
Let’s now look at some code for writing a Person to the Bond serializer.
Note: There is an example of serialization code in the guide to Bond. This shows the helper static methods, Serializer.To and Deserializer.From, however these are not the most optimal for non-trivial code, so I’ll ignore those for my example.
Using clause
Bond includes a Bond.IO.Safe namespace and a Bond.IO.Unsafe, according to the documentation the Unsafe namespace includes the fastest code. So For this example I’m using Bond.IO.Unsafe.
How to write an object to Bond
var src = new Person { FirstName = "Scooby", LastName = "Doo", Age = 7 }; var output = new OutputBuffer(); var writer = new CompactBinaryWriter<OutputBuffer>(output); var serializer = new Serializer<CompactBinaryWriter<OutputBuffer>>(typeof(Person)); serializer.Serialize(src, writer);
The Serialize.To code allows us to dispense with the serializer, but the initial call to this creates the serializer which can take a performance hit if used inside a loop or the likes, hence creating the serializer upfront and using this instance in any loops would provide better overall performance.
How to read an object from Bond
var input = new InputBuffer(output.Data); var reader = new CompactBinaryReader<InputBuffer>(input); var deserializer = new Deserializer<CompactBinaryReader<InputBuffer>>(typeof(Person)); var dst = deserializer.Deserialize(reader);
In the above code we’re getting the input from the OutputBuffer we created from writing data, although this is just to demonstrate usage. The InputBuffer can take a byte[] representing the data to be deserialized.
Where possible InputBuffer’s and OutputBuffer’s should also be reused, simply set the buffer.Position = 0 to reset them after use.
Serialization Protocols
In the previous code we’ve used the CompactBinary classes which implements binary serialization (optimized for compactness, as the name suggests), but there are several other serialization protocols.
FastBinaryReader/FastBinaryWriter, classes are optimized for speed, and easily plug into our sample code like this
var writer = new FastBinaryWriter<OutputBuffer>(output); var serializer = new Serializer<FastBinaryWriter<OutputBuffer>>(typeof(Person));
and
var reader = new FastBinaryReader<InputBuffer>(input); var deserializer = new Deserializer<FastBinaryReader<InputBuffer>>(typeof(Person));
SimpleBinaryReader/SimpleBinaryWriter, classes offer potential for a saving on the payload size.
var writer = new SimpleBinaryWriter<OutputBuffer>(output); var serializer = new Serializer<SimpleBinaryWriter<OutputBuffer>>(typeof(Person));
and
var reader = new SimpleBinaryReader<InputBuffer>(input); var deserializer = new Deserializer<SimpleBinaryReader<InputBuffer>>(typeof(Person));
Human readable serialization protocols
At the time of writing, Bond supports the two “human-readable” based protocols, which are XML and JSON.
Let’s look at the changes required to read/write JSON.
The JSON protocol can be used with the .bond file as previously defined, or we can add JsonName attribute to the fields to produce
namespace Sample struct Person { [JsonName("First")] 0: string FirstName; [JsonName("Last")] 1: string LastName; [JsonName("Age")] 2: int32 Age; }
if we are supporting Json with named attributes. The easiest way to use the SimpleJsonReade/SimpleJsonWriter is using a string buffer (or StringBuilder in C# terms), so here’s the code to write our Person object to a Json string
var sb = new StringBuilder(); var writer = new SimpleJsonWriter(new StringWriter(sb)); var serializer = new Serializer<SimpleJsonWriter>(typeof(Person)); serializer.Serialize(src, writer);
to deserialize the string back to an object we can use
var reader = new SimpleJsonReader(new StringReader(sb.ToString())); var deserializer = new Deserializer<SimpleJsonReader>(typeof(Person)); var dst = deserializer.Deserialize(reader);
The XML protocol can be used with the original .bond file (or the Json one as the JsonName attributes are ignored) so nothing to change there. Here’s the code to write our object to XML (again we’re using a string as a buffer)
var sb = new StringBuilder(); var writer = new SimpleXmlWriter(XmlWriter.Create(sb)); var serializer = new Serializer<SimpleXmlWriter>(typeof(Person)); serializer.Serialize(src, writer); writer.Flush();
and to deserialize the XML we simply use
var reader = new SimpleXmlReader( XmlReader.Create( new StringReader(sb.ToString()))); var deserializer = new Deserializer<SimpleXmlReader>(typeof(Person)); var dst = deserializer.Deserialize(reader);
Transcoding
The Transcoder allows us to convert “payloads” from one protocol to another. For example, let’s assume we’ve got a SimpleXmlReader representing some XML data and we want to transcode it to a CompactBinaryWriter format, we can do the following
var reader = new SimpleXmlReader(XmlReader.Create(new StringReader(xml))); var output = new OutputBuffer(); var writer = new CompactBinaryWriter<OutputBuffer>(output); var transcode = new Transcoder< SimpleXmlReader, CompactBinaryWriter<OutputBuffer>>( typeof(Person)); transcode.Transcode(reader, writer);
Now our payload is represented as a CompactBinaryWriter. Obviously this is more useful in scenarios where you have readers and writers as opposed to this crude example where we could convert to and from the Person object ourselves.
References