Understanding Protobuf Extensions in C#
When working with Protocol Buffers (protobuf) in C#, especial when interoperating between different platforms such as Java and .NET, handling extension fields correctly is crucial. While the generated classes include infrastructure for extensions, accessing them requires using specific utility methods from the ProtoBuf library.
Generated Code and Extension Storage
In protoubf-generated C# classes, extension data is stored in a private field that implements IExtension. This field is not directly accessible, but the IExtensible interface provides controlled access:
private global::ProtoBuf.IExtension extensionObject;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
{
return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing);
}
This pattern ensures type safety and proper lifecycle management of extansion data without exposing internal state publicly.
Writing Extension Data
To attach custom data to an extensible message, use AppendValue:
var request = new Request { type = Request.Type.LOGIN };
var loginRequest = new LoginReq { username = "zhangsan", password = "123456" };
ProtoBuf.Extensible.AppendValue(request, 100, loginRequest);
The second parameter (100) is the field number defined in the .proto file for this extension. It must match on both sender and receiver sides.
Reading Extension Data
To retrieve extension data, use the generic GetValue method:
if (response.status == Response.Status.OK && response.respSuccess != null)
{
var loginResponse = ProtoBuf.Extensible.GetValue<LoginResp>(response.respSuccess, 100);
// Now you can access extended fields
var result = loginResponse.result;
var userInfo = loginResponse.userInfo;
}
This safely deserializes the extension data if present, or returns null if no data exists for the specified field number.
Complete Usage Example
Here's a full example demonstrating TCP communication with protobuf messages containing extensions:
if (ConnectServer("127.0.0.1", 8998))
{
var request = new Request { type = Request.Type.LOGIN };
var loginReq = new LoginReq { username = "zhangsan", password = "123456" };
ProtoBuf.Extensible.AppendValue(request, 100, loginReq);
using (var stream = hrvClient.GetStream())
{
ProtoBuf.Serializer.SerializeWithLengthPrefix(stream, request, ProtoBuf.PrefixStyle.Base128);
var response = ProtoBuf.Serializer.DeserializeWithLengthPrefix<Response>(stream, ProtoBuf.PrefixStyle.Base128);
if (response?.status == Response.Status.OK && response.respSuccess != null)
{
var loginResp = ProtoBuf.Extensible.GetValue<LoginResp>(response.respSuccess, 100);
if (loginResp != null)
{
ProcessLoginResult(loginResp.result, loginResp.userInfo);
}
}
}
}
This approach enables flexible schema evolution and cross-platform compatibility when exchanging complex data structures via protobuf.