4

While working on 2 of my classes that looks like this (minimal)

using System;
using System.Collections.Generic;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using ProtoBuf;

namespace Sandbox
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            Family family = new Family();
            Child child1 = new Child(1);
            Child child2 = new Child(2);
            Parent parent = new Parent(new List<Child>() { child1, child2 });
            family.Add(parent);

            string file = "sandbox.txt";

            try { File.Delete(file); } catch { }

            using (var fs = File.OpenWrite(file)) { Serializer.Serialize(fs, family); }
            using (var fs = File.OpenRead(file)) { family = Serializer.Deserialize<Family>(fs); }

            System.Diagnostics.Debug.Assert(family != null, "1. Expect family not null, but not the case.");
        }
    }

    [ProtoContract()]
    public class Child
    {
        [ProtoMember(1, AsReference = true)]
        internal Parent Parent;

        private Child() { }

        public Child(int i) { }
    }

    [ProtoContract()]
    public class Parent
    {
        [ProtoMember(1)]
        protected List<Child> m_Children;

        /// <summary>
        /// ProtoBuf deserialization constructor (fails here)
        /// </summary>
        private Parent() { m_Children = new List<Child>(); }

        public Parent(List<Child> children)
        {
            m_Children = children;
            m_Children.ForEach(x => x.Parent = this);
        }
    }

    [ProtoContract()]
    public class Family
    {
        [ProtoMember(1)]
        protected List<Parent> m_Parents;

        public void Add(Parent parent)
        {
            m_Parents.Add(parent);
        }

        public Family()
        {
            m_Parents = new List<Parent>();
        }
    }
}

During deserialization, I encounter the exception "No parameterless constructor defined for this object." for creating the Parent object in ProtoBuf.BclHelper near

case FieldObject:
// ...
value = ((options & NetObjectOptions.UseConstructor) == 0) ? BclHelpers.GetUninitializedObject(type) : Activator.CreateInstance(type);

Then when I changed the default constructor Parent() to public, the exception goes away.

Any idea what I may have overlooked is the correct usage for AsRerference in this case?

BOUNTY: While Marc takes his time to fix the issue, I would require a definitive solution to use protobuf-net in this situation, working around either by protobuf-net attributes, methods or other tricks. Otherwise I will have to abandon the use of protobuf-net altogether. Thanks for any help.

Jake
  • 11,273
  • 21
  • 90
  • 147
  • I've just returned from a few days away. I will look at this later. – Marc Gravell Aug 29 '11 at 15:24
  • Looking at this briefly, I think there is an edge case relating to inheritance and reference-tracking; I know how to resolve it - it just needs a few tweaks to fix. – Marc Gravell Aug 30 '11 at 12:39
  • @Marc Thanks for being here. Really apprecatiate your contributions. If you ever get time to have this sorted hopefully you will update the answer here. Thanks again. – Jake Aug 30 '11 at 16:47
  • I expect to fix it in the next day or two – Marc Gravell Aug 30 '11 at 20:14

2 Answers2

3

I believe you can do this to fix this problem:

[ProtoContract(SkipConstructor = true)]
public class Parent
{
    [ProtoMember(1)]
    protected List<Child> m_Children;

    private Parent() { Initialize(); }

    [ProtoBeforeDeserialization] // could also use OnDeserializing
    private void Initialize()
    {
        m_Children = new List<Child>();
    }

    public Parent(List<Child> children)
    {
        m_Children = children;
        m_Children.ForEach(x => x.Parent = this);
    }

}

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Thanks simon. But I do not want to skip the constructor as I have some initialization going on in there. – Jake Sep 23 '11 at 01:34
  • Thanks. That was helpful although the result will be quite messy, definitely not going to be permanant solution. btw, do you happen to know a place for protobuf-net docs? Like it will be good if I can google for "List of protobuf-net attributes" – Jake Sep 23 '11 at 05:36
  • @Jake - yes, it's just a quick fix, obviously the library needs some modifications. And, no, I don't know any secret place for protobuf-net documentation, but .NET Reflector is my god, and the OnDeserializing trick principle is exactly the same as when using WCF, which I happen to know a bit. – Simon Mourier Sep 23 '11 at 05:45
1

When you deserialize Parent, you need the public parameterless constructor. So, your minimal test-case that fails is: create Child that has non-null m_Parent, serialize it and deserialize the result.

svick
  • 236,525
  • 50
  • 385
  • 514
  • sorry but I don't really understand you. but i should have been clearer. I am deserializing Parent. And I had stepped into the PB-net source code and see that it throws when trying to create Parent. – Jake Aug 28 '11 at 15:00
  • Okay, now I don't understand what's going on either. – svick Aug 28 '11 at 15:08
  • also, my minimal test cases pass using private or protected parameterless constructors. I just need to know what other things will indirectly cause private constructors to fail when using AsReference. – Jake Aug 28 '11 at 15:32