问题描述
||
观察以下代码(从此问题中获取):
[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);
}
}
}
显然,代码是错误的,因为b
和c
被声明为object
,但是我使用通用的Serializer.Serialize<T>
方法序列化了它们:
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:
如果我将b
重新声明为B
,而将c
重新声明为C
,则一切正常。但是,我需要将它们声明为object
,所以我想我必须使用非泛型方法Serializer.NonGeneric.SerializeWithLengthPrefix
对其进行序列化,这个问题我不理解该方法期望的额外fieldNumber
参数的含义。有人可以解释一下这是什么,我应该在这里使用它吗?
我使用protobuf-net v2。
谢谢。
编辑
我设法通过添加以下代码使其工作:
RuntimeTypeModel.Default.Add(typeof(object),false).AddSubType(1,typeof(B)).AddSubType(2,typeof(C));
尽管可以工作,但问题是我需要在编译时知道序列化中使用的类型(B = 1,C = 2),这对我来说是不利的。有没有更好的办法?
编辑2
好的,我已经更改了代码,如下所示:
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,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,PrefixStyle.Base128);
typeMap[c.GetType()].SerializeWithLengthPrefix(ms,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);
}
}
现在,为了序列化对象,我不需要任何编译时映射,我只需跳转到相应的通用方法即可。当然,我知道在这种方案中,我必须知道反序列化的类型,但这仍然很麻烦。
解决方法
如果要序列化同类数据,则字段号在很大程度上无关紧要,需要注意的一点是,将其保持为1(又名
Serializer.ListItemTag
),如果需要,可以将其读回列表很简单(但无论哪种方式都非常容易)。
在异类数据的情况下,如本例所示-为此目的设计了一个非通用API(实际上,在v2中,所有API都是非通用的-通用API只是转发给非通用)。通过输入ѭ16,您可以即时告诉它如何解释遇到的任何标签(在流的根处)。如果您选择为给定的标签返回null
,则它假定您对该对象不感兴趣并完全跳过了该对象(显然,在下面的示例中,它将被炸掉-但这仅仅是因为示例代码很少) :
// I\'m giving the example in terms of the v2 API,because there is a bug in the
// Serializer.NonGeneric code in the beta - simply,in the first beta cut this
// doesn\'t correctly forward to the type-model. This will be fixed ASAP.
TypeModel model = RuntimeTypeModel.Default;
using (var ms = new MemoryStream())
{
var tagToType = new Dictionary<int,Type>
{ // somewhere,we need a registry of what field maps to what Type
{1,typeof(B)},{2,typeof(C)}
};
var typeToTag = tagToType.ToDictionary(pair => pair.Value,pair => pair.Key);
object b = new B { Y = 2 };
object c = new C { Y = 4 };
// in v1,comparable to Serializer.NonGeneric.SerializeWithLengthPrefix(ms,b,PrefixStyle.Base128,typeToTag[b.GetType()]);
model.SerializeWithLengthPrefix(ms,null,c,typeToTag[c.GetType()]);
ms.Position = 0;
// in v1,comparable to Serializer.NonGeneric.TryDeserializeWithLengthPrefix(ms,key => tagToType[key],out b2);
object b2 = model.DeserializeWithLengthPrefix(ms,key => tagToType[key]);
object c2 = model.DeserializeWithLengthPrefix(ms,key => tagToType[key]);
Assert.AreEqual(((B)b).Y,((B)b2).Y);
Assert.AreEqual(((C)c).Y,((C)c2).Y);
}