Protocol Buffers In C#: How Are Boxed Value Types Handled
-
05-09-2019 - |
Question
In the following examples:
public class RowData
{
public object[] Values;
}
public class FieldData
{
public object Value;
}
I am curious as how either protobuf-net or dotnet-protobufs would handle such classes. I am more familiar with protobuf-net, so what I actually have is:
[ProtoContract]
public class RowData
{
[ProtoMember(1)]
public object[] Values;
}
[ProtoContract]
public class FieldData
{
[ProtoMember(1)]
public object Value;
}
However I get an error saying "No suitable Default Object encoding found". Is there an easy way to treat these classes, that I am just not aware of?
To elaborate more on the use case:
This is a scaled down version of a data class used in remoting. So essentially it looks like this:
FieldData data = new FieldData();
data.Value = 8;
remoteObject.DoSomething(data);
Note: I've omitted the ISerializable implementation for simplicity, but it is as you'd expect.
Solution
(updated)
Right; figured it out... the main problem in my sample above was the value-getters; they were throwing exceptions. There were also some library glitches (now fixed).
However, the simplest approach is Nullable<T>
pass-thru properties:
[ProtoMember(1)]
private int? ValueInt32
{
get { return Get<int>(); }
set { Value = value; }
}
etc, with:
private T? Get<T>() where T : struct
{
return (Value != null && Value is T) ? (T?)Value : (T?)null;
}
Both this and the *Specified approach have been tested, and now work fine.
OTHER TIPS
Re protobuf-net, which I maintain:
The issue here isn't value-types (which it will often handle fine) - it is the open object
usage, which means it simply doesn't know what data to expect, and thus how to encode/decode it.
At the moment, I can't think of an easy/clean way to handle that. It will handle a range of common value-type scenarios, lists, and any level of hierarchy based on contracts (data-contract, proto-contracts, or some xml-schemas), but it needs a clue.
Perhaps if you can clarify the use-case, I might be able to help more? For example, the above wouldn't work very with DataContractSerializer
or XmlSerializer
either...
Re dotnet-protobufs; I can't really comment, but I'm pretty sure it would be even less forgiving; it is intended to be used with classes generated from a .proto file, so object
would simply never enter into the model (Jon: correct me if I am wrong).
If you do leave more info, could you kindly post a comment here? So I can find it easily... Alternatively, drop me a mail directly (see my SO profile).
edit - here's the hacky thing I had in mind - it isn't working at the moment, but I'll figure out why tomorrow (probably). Note that in theory the extra members could all be private - I'm just trying to make it easy while debugging. Note that this doesn't take any extra storage. Like I say, it doesn't work today, but it should - I'll find out why...
[ProtoContract]
public class FieldData
{
public object Value {get;set;}
[ProtoMember(1)]
public int ValueInt32 {
get { return (int)Value; } set { Value = value; } }
public bool ValueInt32Specified {
get { return Value != null && Value is int; } set { } }
[ProtoMember(2)]
public float ValueSingle {
get { return (float)Value; } set { Value = value; } }
public bool ValueSingleSpecified {
get { return Value != null && Value is float; } set { } }
// etc for expected types
}
This is something like what I had in mind. Let me know what you think. Naturally I'd have to add a subclass for each value type I need to support. What do you think? Is there a better way, do you see any inefficiencies with this method?
[ProtoContract, Serializable]
[ProtoInclude(1, typeof(Int32FieldData))]
public abstract class FieldDataBase : ISerializable
{
[ProtoIgnore]
public abstract object Value { get; set;}
protected FieldDataBase()
{ }
#region ISerializable Members
protected FieldDataBase(SerializationInfo info, StreamingContext context)
{
Serializer.Merge<FieldDataBase>(info, this);
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
Serializer.Serialize<FieldDataBase>(info, this);
}
#endregion
}
[ProtoContract, Serializable]
public class Int32FieldData : FieldDataBase
{
[ProtoMember(1)]
public int? Int32Value;
[ProtoIgnore]
public override object Value
{
get { return this.Int32Value.HasValue ? this.Int32Value : null; }
set { this.Int32Value = (int?)value; }
}
public Int32FieldData() { }
protected Int32FieldData(SerializationInfo info, StreamingContext context)
:base(info, context)
{ }
}
Direct encapsulation seems to work fine with no additional overhead from all the properties, in the following manner:
[ProtoContract, Serializable]
public class ObjectWrapper : ISerializable
{
public ObjectWrapper()
{ }
[ProtoIgnore]
public object Value
{
get
{
if (Int32Value.HasValue)
return Int32Value.Value;
else if (BinaryValue != null)
return BinaryValue;
else
return StringValue;
}
set
{
if (value is int)
this.Int32Value = (int)value;
else if (value is byte[])
this.BinaryValue = (byte[])value;
else if (value is string)
this.StringValue = (string)value;
}
}
[ProtoMember(1)]
private int? Int32Value;
[ProtoMember(2)]
private string StringValue;
[ProtoMember(3)]
private byte[] BinaryValue;
// etc
#region ISerializable Members
protected ObjectWrapper(SerializationInfo info, StreamingContext context)
{
Serializer.Merge<ObjectWrapper>(info, this);
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
Serializer.Serialize<ObjectWrapper>(info, this);
}
#endregion
}