I don't want to mess up our BusinessObjects with any serialization logic. That's why I wanted to implemented a Surrogate for every single BusinessObject which encapsulates the Data which is necessary to serialize and deserialize the BusinessObject. Furthermore it contains all business logic which is to be executed after serialization.
However there seems to be an issue with reference tracking in Dictionaries. Consider the following example:
//DataContracts:
public class SerializeClass
{
public Dictionary<string, SerializeDictionaryItem> MyDictionary { get; set; }
public List<SerializeDictionaryItem> MyList { get; set; }
}
[ProtoContract]
public class SerializeDictionaryItem
{
[ProtoMember(1)]
public string MyField { get; set; }
}
[ProtoContract(SkipConstructor = true)]
public class SerializeClassSurrogate
{
[ProtoMember(1000, AsReference = true)]
public Dictionary<string, SerializeDictionaryItem> MyDictionary { get; set; }
[ProtoMember(1001, AsReference = true)]
public List<SerializeDictionaryItem> MyList { get; set; }
public static implicit operator SerializeClass(SerializeClassSurrogate surrogate)
{
if (surrogate == null)
return null;
var serializeClass = new SerializeClass();
serializeClass.MyDictionary = surrogate.MyDictionary;
serializeClass.MyList = surrogate.MyList;
return serializeClass;
}
public static implicit operator SerializeClassSurrogate(SerializeClass serializeClass)
{
if (serializeClass == null)
return null;
var surrogate = new SerializeClassSurrogate();
surrogate.MyDictionary = serializeClass.MyDictionary;
surrogate.MyList = serializeClass.MyList;
return surrogate;
}
}
//Serialization Logic:
RuntimeTypeModel.Default[typeof(SerializeClass)].SetSurrogate(typeof(Surrogates.SerializeClassSurrogate));
var myDictionaryItem = new SerializeDictionaryItem();
myDictionaryItem.MyField = "ABC";
SerializeClass m = new SerializeClass() {MyDictionary = new Dictionary<string, SerializeDictionaryItem>()};
m.MyDictionary.Add("def", myDictionaryItem);
m.MyDictionary.Add("abc", myDictionaryItem);
m.MyList = new List<SerializeDictionaryItem>();
m.MyList.Add(myDictionaryItem);
m.MyList.Add(myDictionaryItem);
using (var writer = new StreamWriter(OutputDir + "proto.bin"))
{
Serializer.Serialize(writer.BaseStream, m);
}
using(var reader = new StreamReader(OutputDir + "proto.bin"))
{
var deserialized = Serializer.Deserialize<SerializeClass>(reader.BaseStream);
var areEqualInDictionary = deserialized.MyDictionary["def"] == deserialized.MyDictionary["abc"];
var areEqualInList = deserialized.MyList[0] == deserialized.MyList[1];
}
The two "areEqualIn..." booleans should be true after the deserialization. However I get the following exception during deserialization:
"A reference-tracked object changed reference during deserialization"
Is this a bug or am I doing something wrong?
/EDIT: This doesn't seem to have anything to do with the usage of Surrogates. If I try the serialization without them and simply change the SerializeClass to a ProtoContract and its Members accordingly to ProtoMembers exactly the same problem occurs.
Btw. I am using protobuf-net v2 429
/EDIT2: This exception can also be provoked using this code, which doesn't need Dictionaries at all:
public class SerializeClass
{
public string MyField { get; set; }
public SerializeClass GroupTrailerRef { get; set; }
}
[ProtoContract(SkipConstructor = true)]
public class SerializeClassSurrogate
{
[ProtoMember(1001)]
public string MyField { get; set; }
[ProtoMember(1002, AsReference = true)]
public SerializeClass GroupTrailerRef { get; set; }
public static implicit operator SerializeClass(SerializeClassSurrogate surrogate)
{
if (surrogate == null)
return null;
var serializeClass = new SerializeClass();
serializeClass.MyField = surrogate.MyField;
serializeClass.GroupTrailerRef = surrogate.GroupTrailerRef;
return serializeClass;
}
public static implicit operator SerializeClassSurrogate(SerializeClass serializeClass)
{
if (serializeClass == null)
return null;
var surrogate = new SerializeClassSurrogate();
surrogate.MyField = serializeClass.MyField;
surrogate.GroupTrailerRef = serializeClass.GroupTrailerRef;
return surrogate;
}
}
RuntimeTypeModel.Default[typeof(SerializeClass)].SetSurrogate(typeof(Surrogates.SerializeClassSurrogate));
//Serialization Code:
SerializeClass groupTrailer = new SerializeClass();
groupTrailer.MyField = "Group";
SerializeClass m = new SerializeClass() {};
m.GroupTrailerRef = groupTrailer;
m.MyField = "First";
SerializeClass k = new SerializeClass();
k.GroupTrailerRef = groupTrailer;
k.MyField = "Second";
var lst = new List<SerializeClass>();
lst.Add(m);
lst.Add(k);
using (var writer = new StreamWriter(OutputDir + "proto.bin"))
{
Serializer.Serialize(writer.BaseStream, lst);
}
using(var reader = new StreamReader(OutputDir + "proto.bin"))
{
var deserialized = Serializer.Deserialize<List<SerializeClass>>(reader.BaseStream);
var areEqual = deserialized[0].GroupTrailerRef == deserialized[1].GroupTrailerRef;
}
What do you suggest? I like the surrogate method to separate the serialization logic from my business objects. Do you think I should just stick to the "normal" way? However in that case I am again stuck because of Issue 203...
Thank you, TH