Reputation: 325
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
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
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