问题
I have come across a problem with protobuf-net and have narrowed it down to this simplest case. I want a linked list type structure, where a class has a property of the same type. When I serialize this it works great. However if the type is an interface instead of a class I get the following error: The type cannot be changed once a serializer has been generated for ConsoleApplication1.foo (ConsoleApplication1.ifoo)
This is the code I have to generate this error:
class Program
{
static void Main(string[] args)
{
var toSerialize = new foo();
toSerialize.data = "foo1";
var subf = new foo();
subf.data = "foo2";
toSerialize.subfoo = subf;
using (var pbfsc = new FileStream("testfile.proto", FileMode.Create))
{
using (var cs = new GZipStream(pbfsc, CompressionMode.Compress))
{
ProtoBuf.Serializer.Serialize(cs, toSerialize);
}
pbfsc.Close();
}
}
}
[ProtoContract, ProtoInclude(2000, typeof(foo))]
public interface ifoo
{
[ProtoMember(1)]
string data { get; set; }
[ProtoMember(2)]
ifoo subfoo { get; set; }
}
[ProtoContract]
public class foo : ifoo
{
[ProtoMember(1)]
public string data { get; set; }
[ProtoMember(2)]
public ifoo subfoo { get; set; }
}
I have done all the reading on the subject I can and I can't see what I have done wrong. I tried putting my object in a wrapper class as it looks similiar to a problem with lists of interfaces (code not shown) and this still didn't help.
If I change subfoo to type foo then it works fine, but in my more complex real world problem I would rather stick to using interface. Have I done something wrong here or is this a problem with protobuf-net?
Any help greatly appreciated.
Cheers
Alex
回答1:
There is some outstanding work that needs completing to improve the treatment of the top level object when it implements an interface that is a contract. Basically, at the moment it only processes the non-interface parts of the root object, but handles interfaces for properties / sub-objects etc.
The exception you are seeing is slightly odd - I would expect it to have known about ifoo
in time, due to the subfoo
property, but the fundamental problem here can be avoided by adding:
Serializer.PrepareSerializer<ifoo>();
before the serialization code (ideally somewhere pretty early; you only need to call it once).
However, I should also note that you're actually double-serializing here: when serializing the ifoo subfoo
property, it will serialize the decorated members from both the interface (ifoo
) and the concrete type (foo
). These are actually the same values, so there is some redundancy here.
I would say: take the member-level attributes off the concrete type, but the root-object glitch makes this slightly problematic. Another fix, that solves both issues (no longer requiring the PrepareSerializer
) is:
using ProtoBuf;
using System;
class Program
{
static void Main(string[] args)
{
var root = new foo();
root.data = "foo1";
var subf = new foo();
subf.data = "foo2";
root.subfoo = subf;
var toSerialize = new FooRoot { root = root };
// this does the same as your file-code, but runs
// both serialize and deserialize - basicaly, it is
// a lazy way of checking it end-to-end
var clone = Serializer.DeepClone(toSerialize).root;
Console.WriteLine(clone.data); // "foo1"
Console.WriteLine(clone.subfoo.data); // "foo2"
}
}
[ProtoContract]
public class FooRoot
{
[ProtoMember(1)]
public ifoo root { get; set; }
}
[ProtoContract, ProtoInclude(2000, typeof(foo))]
public interface ifoo
{
[ProtoMember(1)]
string data { get; set; }
[ProtoMember(2)]
ifoo subfoo { get; set; }
}
[ProtoContract]
public class foo : ifoo
{
public string data { get; set; }
public ifoo subfoo { get; set; }
}
When the root-object/interface-support glitch is resolved, the FooRoot
wrapper would not be needed, but I'll probably need to add some switch to enable/disable the fix, for legacy support.
来源:https://stackoverflow.com/questions/12623178/protobuf-net-and-serializing-a-linked-list-using-interfaces