Google Protocol Buffers is a proven protocol for serializing data efficiently. It has a wide adoption, enabling serialization for almost every platform, making the data easy to exchange between platforms. To store its schema, you can use .proto files, that enable describing messages in a platform agnostic format. You can see an example below:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
Sometimes you want to define a message that will have only one of its fields initialized. This can be useful when sending a message providing a wrap around multiple types or in any other case that requires it. Take a look at the example, providing a wrap around three messages of the following types: MessageA, MessageB and MessageC.
message Wrapper{
oneof OnlyOneOf {
MessageA a = 1;
MessageB b = 2;
MessageC c = 3;
}
}
Protocol buffers use a tag value to store any field. First, the tag (1, 2, 3 above) and the type of the field is stored, next, the value is written. In the following case, where only one field is assigned, it would write its tag, type and value. Do we need to create a class that will contain all the fields? Do we need to waste this space for storing fields?
To fix this wasteful generation, protobuf-net, a library build by Marc Gravell, added Discriminated Union types that is capable of addressing it. Let’s just take a look at the first of them.
public struct DiscriminatedUnionObject
{
private readonly int _discriminator;
// The value typed as Object
public readonly object Object;
// Indicates whether the specified discriminator is assigned
public bool Is(int discriminator) => _discriminator == ~discriminator;
// Create a new discriminated union value
public DiscriminatedUnionObject(int discriminator, object value)
{
_discriminator = ~discriminator; // avoids issues with default value / 0
Object = value;
}
}
Let’s walk through all the design choices that have been made here:
DiscriminatedUnionObject
is a struct
. It means, that if a class have a field of this type, it will be stored in the object, without additional allocations (you can think of it as inlining the structure, creating a “fat object”)Object
. (no matter which type is it)._discriminator
to store the tag of the field.If you generated the Wrapper class, it’d have only one field, of the DiscriminatedUnionObject
type. Once a message of a specified type is set, the discriminator and the value would be written in the union. Simple, and efficient.
Mapping a generic idea, like a discriminated union, into a platform or a language isn’t simple. Again, once it’s made in an elegant and an efficient way, I truly believe that it’s worth to be named as a pearl.