我使用Protobuf使用WebSocket在我的Web客户端和服务器(C#)之间进行通信。在客户端上,de / serialization通过Protobuf.js完成,并在服务器上使用protobuf-net。
问题在于,当使用抽象类聚合时,protobuf-net不能反序化Protobuf.js发送的数据。
这是堆栈跟踪:
ProtoException: No parameterless constructor found for Base.
at ProtoBuf.Meta.TypeModel.ThrowCannotCreateInstance(Type type) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 1397
at proto_6(Object , ProtoReader )
at ProtoBuf.Serializers.CompiledSerializer.ProtoBuf.Serializers.IProtoSerializer.Read(Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Serializers\CompiledSerializer.cs:line 57
at ProtoBuf.Meta.RuntimeTypeModel.Deserialize(Int32 key, Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Meta\RuntimeTypeModel.cs:line 775
at ProtoBuf.ProtoReader.ReadTypedObject(Object value, Int32 key, ProtoReader reader, Type type) na c:\Dev\protobuf-net\protobuf-net\ProtoReader.cs:line 579
at ProtoBuf.ProtoReader.ReadObject(Object value, Int32 key, ProtoReader reader) na c:\Dev\protobuf-net\protobuf-net\ProtoReader.cs:line 566
at proto_2(Object , ProtoReader )
at ProtoBuf.Serializers.CompiledSerializer.ProtoBuf.Serializers.IProtoSerializer.Read(Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Serializers\CompiledSerializer.cs:line 57
at ProtoBuf.Meta.RuntimeTypeModel.Deserialize(Int32 key, Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Meta\RuntimeTypeModel.cs:line 775
at ProtoBuf.Meta.TypeModel.DeserializeCore(ProtoReader reader, Type type, Object value, Boolean noAutoCreate) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 700
at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type, SerializationContext context) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 589
at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 566
at ProtoBuf.Serializer.Deserialize[T](Stream source) na c:\Dev\protobuf-net\protobuf-net\Serializer.cs:line 77
at ProtobufPolymorphismTest.Program.Main(String[] args) na c:\Desenvolvimento\Testes\ProtobufPolymorphismTest\ProtobufPolymorphismTest\Program.cs:line 30
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
这是合同:
[ProtoContract]
[ProtoInclude(100, typeof(Child))]
abstract class Base
{
[ProtoMember(1)]
public int BaseProperty { get; set; }
}
[ProtoContract]
class Child : Base
{
[ProtoMember(1)]
public float ChildProperty { get; set; }
}
[ProtoContract]
class Request
{
[ProtoMember(1)]
public Base Aggregate { get; set; }
}
这是重现错误的代码。由于序列化工作正常,我只将结果作为字节数组提供。如果有帮助,我可以提供我为获取序列化值而采取的步骤。
// This is the object serialized
Child child = new Child() { ChildProperty = 0.5f, BaseProperty = 10 };
Request request = new Request() { Aggregate = child };
// This is the byte representation generated by protobuf-net and Protobuf.js
byte[] protoNet = new byte[] { 10, 10, 162, 6, 5, 13, 0, 0, 0, 63, 8, 10 };
byte[] protoJS = new byte[] { 10, 10, 8, 10, 162, 6, 5, 13, 0, 0, 0, 63 };
// Try to deserialize the protobuf-net data
using (System.IO.MemoryStream ms = new System.IO.MemoryStream(protoNet))
{
request = Serializer.Deserialize<Request>(ms); // Success
}
// Try to deserialize the Protobuf.js data
using (System.IO.MemoryStream ms = new System.IO.MemoryStream(protoJS))
{
request = Serializer.Deserialize<Request>(ms); // ProtoException: No parameterless constructor found for Base.
}
如果我在Base类定义中添加SkipConstructor = true,则错误更改为&#34; MemberAccessException:无法创建抽象类&#34;使用以下堆栈跟踪。如果我从Base类定义中删除抽象,它将按预期工作。
System.MemberAccessException: Cannot create an abstract class.
at System.Runtime.Serialization.FormatterServices.nativeGetUninitializedObject(RuntimeType type)
at ProtoBuf.BclHelpers.GetUninitializedObject(Type type) na c:\Dev\protobuf-net\protobuf-net\BclHelpers.cs:line 38
at proto_6(Object , ProtoReader )
at ProtoBuf.Serializers.CompiledSerializer.ProtoBuf.Serializers.IProtoSerializer.Read(Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Serializers\CompiledSerializer.cs:line 57
at ProtoBuf.Meta.RuntimeTypeModel.Deserialize(Int32 key, Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Meta\RuntimeTypeModel.cs:line 775
at ProtoBuf.ProtoReader.ReadTypedObject(Object value, Int32 key, ProtoReader reader, Type type) na c:\Dev\protobuf-net\protobuf-net\ProtoReader.cs:line 579
at ProtoBuf.ProtoReader.ReadObject(Object value, Int32 key, ProtoReader reader) na c:\Dev\protobuf-net\protobuf-net\ProtoReader.cs:line 566
at proto_2(Object , ProtoReader )
at ProtoBuf.Serializers.CompiledSerializer.ProtoBuf.Serializers.IProtoSerializer.Read(Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Serializers\CompiledSerializer.cs:line 57
at ProtoBuf.Meta.RuntimeTypeModel.Deserialize(Int32 key, Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Meta\RuntimeTypeModel.cs:line 775
at ProtoBuf.Meta.TypeModel.DeserializeCore(ProtoReader reader, Type type, Object value, Boolean noAutoCreate) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 700
at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type, SerializationContext context) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 589
at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 566
at ProtoBuf.Serializer.Deserialize[T](Stream source) na c:\Dev\protobuf-net\protobuf-net\Serializer.cs:line 77
at ProtobufPolymorphismTest.Program.Main(String[] args) na c:\Desenvolvimento\Testes\ProtobufPolymorphismTest\ProtobufPolymorphismTest\Program.cs:line 30
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
我不确定为什么通过protobuf-net和Protobuf.js生成的二进制表示不同,但是如果Base类不是抽象的,它们看起来都是有效的。
关于为什么会发生这种情况的想法或解决方法而不从Base类中删除抽象?
提前致谢!
更新
这是我用来通过Protobuf.js生成字节序列化的代码:
<script src="//raw.githubusercontent.com/dcodeIO/ByteBuffer.js/master/dist/ByteBufferAB.min.js"></script>
<script src="//cdn.rawgit.com/dcodeIO/ProtoBuf.js/master/dist/ProtoBuf.js"></script>
<script type="text/javascript">
// Proto file
var proto = "";
proto += "package ProtobufPolymorphismTest;\r\n\r\n";
proto += "message Base {\r\n";
proto += " optional int32 BaseProperty = 1 [default = 0];\r\n";
proto += " // the following represent sub-types; at most 1 should have a value\r\n";
proto += " optional Child Child = 100;\r\n";
proto += "}\r\n\r\n";
proto += "message Child {\r\n";
proto += " optional float ChildProperty = 1 [default = 0];\r\n";
proto += "}\r\n\r\n";
proto += "message Request {\r\n";
proto += " optional Base Aggregate = 1;\r\n";
proto += "}";
// Build the entities
var protoFile = dcodeIO.ProtoBuf.loadProto(proto);
var requestClass = protoFile.build("ProtobufPolymorphismTest.Request");
var baseClass = protoFile.build("ProtobufPolymorphismTest.Base");
var childClass = protoFile.build("ProtobufPolymorphismTest.Child");
// Build the request
var base = new baseClass();
base.BaseProperty = 10;
base.Child = new childClass();
base.Child.ChildProperty = 0.5;
var request = new requestClass();
request.Aggregate = base;
// Serialize
var bytes = new Uint8Array(request.toArrayBuffer());
var str = "new byte[] { " + bytes.join(", ") + " };";
console.log(str);
</script>
解决方法
正如Marc所解释的那样,当字段顺序颠倒时,protobuf-net不支持多态性。作为一种特定于Protobuf.js的变通方法,您可以更改.proto文件中字段的顺序,以便按正确的顺序进行序列化。
在我的情况下,将.proto文件更改为以下内容解决了问题:
package ProtobufPolymorphismTest;
message Base {
// the following represent sub-types; at most 1 should have a value
optional Child Child = 100;
optional int32 BaseProperty = 1 [default = 0];
}
message Child {
optional float ChildProperty = 1 [default = 0];
}
message Request {
optional Base Aggregate = 1;
}
(注意optional Child Child = 100;
之前的BaseProperty
答案 0 :(得分:1)
它的长短是protobuf-net的多态支持期望子类型在消息中第一(或者更具体地说:对于任何对象,类型将在数据之前被修复提供)。在js输出中,BaseProperty
的字段数据优先 - 可能完全合理。但是由于没有关于继承应该如何表现的总体协议定义,所以protobuf-net的实现只是真正用于自己。就字节而言,这实际上归结为字段标记“162,6”(以及相关长度/数据,“5,13,0,0,0,63”)出现的位置。
库可以可能被重新设计以允许任何字段顺序进行多态,但是:它需要一些努力。我知道通常期望以任何顺序处理字段,但由于这已经超出了规范,我没有关注这一点。所有其他数据字段都以任何顺序被接受 - 只有多态才能以这种方式工作。
在一般情况下:因为多态性不是规范的一部分,所以我强烈建议在库之间工作时避免使用多态。
注意:您可以通过确保多态性字段低于(数字)而不是数据字段来强制执行此操作。