Oron Nadiv
Oron Nadiv

Reputation: 325

Protocol buffer serialization of List

What's wrong with the following code?

MyList l = Serializer.DeepClone(new MyList { "abc" });
Console.WriteLine(l.Count == 1);

Where:

[ProtoContract]
public class MyList : List<String>
{
}

Expected: true, actual: false.

Upvotes: 3

Views: 371

Answers (2)

Marc Gravell
Marc Gravell

Reputation: 1064204

If you want an immediate fix, just take away the [ProtoContract]; lists have inbuilt behaviour, and don't need to be marked as contracts.

It looks like in this case the contract definition is taking precedence over the fact that it is a list; I will need to look carefully to see if changing this causes any unwanted side-effects.

Working example:

public class MyList : List<string>{}

[Test]        
public void ListSubclassShouldRoundTrip()
{
    var list = new MyList { "abc" };
    var clone = Serializer.DeepClone(list);
    Assert.AreEqual(1, clone.Count);
    Assert.AreEqual("abc", clone[0]);
}

currently broken example:

[ProtoContract]
public class MyContractList : List<string> { }

[Test]
public void ContractListSubclassShouldRoundTrip()
{
    var list = new MyContractList { "abc" };
    var clone = Serializer.DeepClone(list);
    Assert.AreEqual(1, clone.Count);
    Assert.AreEqual("abc", clone[0]);
}

Update; however, it looks like this should work, since it works fine when the list is a member of a type; the problem only occurs when the list is the outermost type in a graph. Example (all tests pass):

[ProtoContract]
public class ListWrapper
{
    [ProtoMember(1)]
    public List<string> BasicList { get; set; }
    [ProtoMember(2)]
    public MyList MyList { get; set; }
    [ProtoMember(3)]
    public MyContractList MyContractList { get; set; }
}

[Test]
public void TestBasicListAsMember()
{
    var obj = new ListWrapper { BasicList = new List<string> { "abc" } };
    var clone = Serializer.DeepClone(obj);
    Assert.IsNull(clone.MyList);
    Assert.IsNull(clone.MyContractList);
    Assert.AreEqual(1, clone.BasicList.Count);
    Assert.AreEqual("abc", clone.BasicList[0]);
}

[Test]
public void TestMyListAsMember()
{
    var obj = new ListWrapper { MyList = new MyList { "abc" } };
    var clone = Serializer.DeepClone(obj);
    Assert.IsNull(clone.BasicList);
    Assert.IsNull(clone.MyContractList);
    Assert.AreEqual(1, clone.MyList.Count);
    Assert.AreEqual("abc", clone.MyList[0]);
}

[Test]
public void TestMyContractListAsMember()
{
    var obj = new ListWrapper { MyContractList = new MyContractList { "abc" } };
    var clone = Serializer.DeepClone(obj);
    Assert.IsNull(clone.BasicList);
    Assert.IsNull(clone.MyList);
    Assert.AreEqual(1, clone.MyContractList.Count);
    Assert.AreEqual("abc", clone.MyContractList[0]);
}

Update update: this was a subtle bug, caused by the many different ways that lists can behave; in the case of lists as members that have getters and setters, it has some logic that says "if we get the list, and it wasn't null, don't call the setter - just add". However, the code that handles types-that-are-lists was accidentally enabling that mode. So what was happening was: it would deserialize the data, then proclaim "you can discard this value" - which it dutifully did. Then the "never return null" code would just create a new list, and return that instead. Annoyingly, just a one-liner to fix, and no side-effects; fixed in r555.

Whenever someone gets stuck, I add an integration test; so this shouldn't happen again

Upvotes: 2

Paul Farry
Paul Farry

Reputation: 4768

I have just finished a project myself where I was working with Protobut and used about 30different Protocontracts in the project and just reviewed this because I thought I'd done similar, but infact I'd never done this particular case directly.

So I just knocked up this as a test, but you may be able to use it for your purposes. (Yeah it's VB, but i'm currently quicker knocking up VB vs C# [but i'm getting there])

The results were not what I expected, so I posted my tests here. Maybe you could implement the Class with Internal list (to get you through for now).

Mylist ProtoBuf False

Mylist BinaryFormatter True

Mylist2 ProtoBuf True

MyListI ProtoBuf False

MyListThing ProtoBuf False

Imports System.IO

Imports ProtoBuf

Public Class Entry
    Shared Sub Main()
        Dim l As New MyList
        l.Add("abc")
        Dim newList As MyList

        Using ms As New MemoryStream
            Serializer.Serialize(Of MyList)(ms, l)
            ms.Seek(0, SeekOrigin.Begin)
            newList = Serializer.Deserialize(Of MyList)(ms)
        End Using
        Console.WriteLine("Mylist ProtoBuf {0}", newList.Count = 1)

        Dim f As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
        Using ms As New MemoryStream
            f.Serialize(ms, l)
            ms.Seek(0, SeekOrigin.Begin)
            newList = CType(f.Deserialize(ms), MyList)
        End Using

        Console.WriteLine("Mylist BinaryFormatter {0}", newList.Count = 1)

        Dim l2 As New MyList2
        l2.Items.Add("abc")
        Dim newList2 As MyList2

        Using ms As New MemoryStream
            Serializer.Serialize(Of MyList2)(ms, l2)
            ms.Seek(0, SeekOrigin.Begin)
            newList2 = Serializer.Deserialize(Of MyList2)(ms)
        End Using
        Console.WriteLine("Mylist2 ProtoBuf {0}", newList2.Items.Count = 1)

        Dim li As New MyListI
        li.Add(5)
        Dim newListi As MyListI

        Using ms As New MemoryStream
            Serializer.Serialize(Of MyListI)(ms, li)
            ms.Seek(0, SeekOrigin.Begin)
            newListi = Serializer.Deserialize(Of MyListI)(ms)
        End Using
        Console.WriteLine("MyListI ProtoBuf {0}", newListi.Count = 1)

        Dim lh As New MyListThing
        lh.Add(New Thing() With {.Message = "abc"})
        Dim newListh As MyListThing

        Using ms As New MemoryStream
            Serializer.Serialize(Of MyListThing)(ms, lh)
            ms.Seek(0, SeekOrigin.Begin)
            newListh = Serializer.Deserialize(Of MyListThing)(ms)
        End Using
        Console.WriteLine("MyListThing ProtoBuf {0}", newListh.Count = 1)
    End Sub
End Class

<ProtoContract(), Serializable()>
Public Class MyList
    Inherits List(Of String)
End Class

<ProtoContract()>
Public Class MyList2
    Public Sub New()
        Items = New List(Of String)
    End Sub
    <ProtoMember(1)>
    Public Property Items As List(Of String)
End Class

<ProtoContract()>
Public Class MyListI
    Inherits List(Of Integer)
End Class

<ProtoContract()>
Public Class MyListThing
    Inherits List(Of Thing)
End Class

<ProtoContract()>
Public Class Thing
    <ProtoMember(1)>
    Public Property Message As String
End Class

Upvotes: 1

Related Questions