Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"An item with the same key has already been added" error with protobuf-net

I'm trying to replace an existing serializer with protobuf for C# by Marc Gravell. My code is extensive and my goal is to be able to do the switch with minimal changes.

I came across an issue which I believe I understand why it happens but require help with overcoming - particularly a solution that would require the least changes in my already existing code and classes. My code is complex so I created the following short sample to demonstrate the issue:

using System;
using System.Collections.Generic;
using System.IO;
using ProtoBuf;


namespace ConsoleApplication1
{
    class program_issue
    {

    [ProtoContract]
    public class Father
    {
        public Father()
        {
            sonny = new Son();
        }

        [ProtoMember(101)]
        public string Name;

        [ProtoMember(102)]
        public Son sonny;

    }

    [ProtoContract]
    public class Son
    {
        public Son()
        {
            Dict.Add(10, "ten");
        }

        [ProtoMember(103)]
        public Dictionary<int, string> Dict = new Dictionary<int, string>();
    }


    static void Main(string[] args)
    {
        Father f1 = new Father();
        f1.Name = "Hello";
        byte[] bts = PBSerializer.Serialize(typeof(Father), f1);

        Father f2;
        PBSerializer.Deserialize(bts, out f2);

    }


    public static class PBSerializer
    {
        public static byte[] Serialize(Type objType, object obj)
        {
            MemoryStream stream = new MemoryStream();
            ProtoBuf.Serializer.Serialize(stream, obj);
            string s = Convert.ToBase64String(stream.ToArray());
            byte[] bytes = stream.ToArray();
            return bytes;
        }


        public static void Deserialize(byte[] data, out Father obj)
        {
            using (MemoryStream stream = new MemoryStream(data))
            {
                obj = ProtoBuf.Serializer.Deserialize<Father>(stream);
            }

        }
    }

}
}

In short, when the parent object is created, it creates a son object which inits a dictionary with some values. I assume that when protobuf tries to rebuild the object when deserializing it uses the same constructor (thus also initiating the dictionary with values) and then tries to push the same values again as part of the deserializing -> error.

How can I overcome it with minimal changes to my code?

Kind regards, Yossi.

like image 972
yossic Avatar asked Nov 01 '11 11:11

yossic


1 Answers

The easiest option here is probably:

[ProtoContract(SkipConstructor = true)]

which will, as it says, not execute the constructor (or field-initializers). Note that this will leave the dictionary null if there is no data. Another approach might be to use a serialization callback (which fires just before it gets loaded with data):

[ProtoBeforeDeserialization]
private void Foo()
{
    Dict.Clear();
}

A third option would be to combine the above by using:

[ProtoContract(SkipConstructor = true)]

and:

[ProtoAfterDeserialization]
private void Foo()
{
    if(Dict == null) Dict = new Dictionary<int,string>();
}

to default it to an empty dictionary even if there was no data. Note you would need to do this from Father too, since that uses the default Son constructor.

like image 145
Marc Gravell Avatar answered Oct 30 '22 13:10

Marc Gravell