Observe the following code (taken from this question):
[ProtoContract]
public class B
{
[ProtoMember(1)] public int Y;
}
[ProtoContract]
public class C
{
[ProtoMember(1)] public int Y;
}
class Program
{
static void Main()
{
object b = new B { Y = 2 };
object c = new C { Y = 4 };
using (var ms = new MemoryStream())
{
Serializer.SerializeWithLengthPrefix(ms, b, PrefixStyle.Base128);
Serializer.SerializeWithLengthPrefix(ms, c, PrefixStyle.Base128);
ms.Position = 0;
var b2 = Serializer.DeserializeWithLengthPrefix<B>(ms, PrefixStyle.Base128);
Debug.Assert(((B)b).Y == b2.Y);
var c2 = Serializer.DeserializeWithLengthPrefix<C>(ms, PrefixStyle.Base128);
Debug.Assert(((C)c).Y == c2.Y);
}
}
}
Obviously, the code is wrong, because b and c are declared as object, but I serialize them using the generic Serializer.Serialize<T> method:
System.ArgumentOutOfRangeException occurred
Message=Specified argument was out of the range of valid values.
Parameter name: index
Source=protobuf-net
ParamName=index
StackTrace:
at ProtoBuf.Meta.BasicList.Node.get_Item(Int32 index)
InnerException:
Everything works fine if I redeclare b as B and c as C. However, I need them to be declared as object, so I guess I have to serialize them using the non generic method Serializer.NonGeneric.SerializeWithLengthPrefix, the problem I do not understand the meaning of the extra fieldNumber argument expected by the method. Can someone explain what is it and how should I use it here?
I use protobuf-net v2.
Thanks.
EDIT
I have managed to make it work with the addition of the following code:
RuntimeTypeModel.Default.Add(typeof(object), false).AddSubType(1, typeof(B)).AddSubType(2, typeof(C));
Although it works, the problem is that I need to know at compile time the types used in the serialization (B = 1, C = 2), which is bad for me. Is there a better way?
EDIT2
OK, I have changed the code like so:
public class GenericSerializationHelper<T> : IGenericSerializationHelper
{
void IGenericSerializationHelper.SerializeWithLengthPrefix(Stream stream, object obj, PrefixStyle prefixStyle)
{
Serializer.SerializeWithLengthPrefix(stream, (T)obj, prefixStyle);
}
}
public interface IGenericSerializationHelper
{
void SerializeWithLengthPrefix(Stream stream, object obj, PrefixStyle prefixStyle);
}
...
static void Main()
{
var typeMap = new Dictionary<Type, IGenericSerializationHelper>();
typeMap[typeof(B)] = new GenericSerializationHelper<B>();
typeMap[typeof(C)] = new GenericSerializationHelper<C>();
object b = new B { Y = 2 };
object c = new C { Y = 4 };
using (var ms = new MemoryStream())
{
typeMap[b.GetType()].SerializeWithLengthPrefix(ms, b, PrefixStyle.Base128);
typeMap[c.GetType()].SerializeWithLengthPrefix(ms, c, PrefixStyle.Base128);
ms.Position = 0;
var b2 = Serializer.DeserializeWithLengthPrefix<B>(ms, PrefixStyle.Base128);
Debug.Assert(((B)b).Y == b2.Y);
var c2 = Serializer.DeserializeWithLengthPrefix<C>(ms, PrefixStyle.Base128);
Debug.Assert(((C)c).Y == c2.Y);
}
}
Now, in order to serialize the objects I do not need any compile time mapping, I just jump to the respective generic method. Of course, I understand that in this scheme I must know the types at deserialization, which is still a bummer.
If you are serializing homogeneous data, the field-number is largely irrelevant, with the minor caveat that keeping it as 1 (aka
Serializer.ListItemTag) it is trivial to read it back as a list if you want (but pretty easy either way).In the case of heterogeneous data, as in this example – there is a non-generic API designed for this purpose (in fact, in v2 all the APIs are non-generic – the generic API simply forwards to the non-generic). By passing in a
TypeResolver, you can tell it on the fly how to interpret any tag encountered (at the root of the stream). If you choose to returnnullfor a given tag, it assumes you aren’t interested in that object and skips it entirely (in the example below it will just blow up, obviously – but that is just because the example code is minimal):