问题
I have a classes like:
[Serializable]
public class child {
public Parent parent;
}
[Serializable]
public class Parent {
public List<child> children;
}
When I deserialize Parent, I want each of each children to have a reference to it's parent. Question is, where in the deserialization process can I set the child's "parent" pointer? I can't seem to use a custom constructor for child, because deserialization always uses the default constructor. If I implement ISerializable, then it seems that the child objects have already been created by the time the parent is created. Is there another way to achieve this?
回答1:
Circular references are handled differently for the BinaryFormatter
, XmlSerializer
and DataContractSerializer
.
The BinaryFormatter supports circular references by default, no work required:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
public class Child
{
public Guid Id { get; set; }
public Parent parent;
}
[Serializable]
public class Parent
{
public Guid Id;
public List<Child> Children;
}
class Program
{
static void Main(string[] args)
{
Child c1 = new Child { Id = Guid.NewGuid() };
Child c2 = new Child { Id = Guid.NewGuid() };
Parent p = new Parent { Id = Guid.NewGuid(), Children = new List<Child> { c1, c2 } };
c1.parent = p;
c2.parent = p;
using (var stream1 = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream1, p);
stream1.Position = 0;
var deserializedParent = formatter.Deserialize(stream1) as Parent;
foreach (var child in deserializedParent.Children)
{
Console.WriteLine("Child Id: {0}, Parent Id: {1}", child.Id, child.parent.Id);
}
}
Console.ReadLine();
}
}
When using the XmlSerializer, avoid the circular refernce by not serializing the child's reference to the parent and ensure that the relationship is fixed during the deserialization process. This is done by implementing the IXmlSerializable interface and handling serialization and deserialization.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
namespace XmlSerialization
{
[Serializable]
public class Child
{
public Guid Id { get; set; }
[XmlIgnore] // Don't serialize the reference to the parent
public Parent parent;
}
[Serializable]
public class Parent : IXmlSerializable
{
public List<Child> Children;
public Guid Id;
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XElement xml = XElement.ReadFrom(reader) as XElement;
if (xml != null)
{
// Deserialize Children
Children =
xml.Descendants("Child")
.Select(x => new Child() { Id = Guid.Parse(x.Element("Id").Value), parent = this })
.ToList();
// Deserialize Id
Id = Guid.Parse(xml.Attribute("Id").Value);
}
}
public void WriteXml(System.Xml.XmlWriter writer)
{
// Serialize Id
writer.WriteAttributeString("Id", Id.ToString());
// Serialize Children
XmlSerializer childSerializer = new XmlSerializer(typeof(Child));
foreach (Child child in Children)
{
childSerializer.Serialize(writer, child);
}
}
}
class Program
{
static void Main(string[] args)
{
Child c1 = new Child { Id = Guid.NewGuid() };
Child c2 = new Child { Id = Guid.NewGuid() };
Parent p = new Parent { Id = Guid.NewGuid(), Children = new List<Child> { c1, c2 } };
c1.parent = p;
c2.parent = p;
using (var stream1 = new MemoryStream())
{
XmlSerializer formatter = new XmlSerializer(typeof(Parent), new Type[] { typeof(Child) }) ;
formatter.Serialize(stream1, p);
stream1.Position = 0;
stream1.Position = 0;
var deserializedParent = formatter.Deserialize(stream1) as Parent;
foreach (var child in deserializedParent.Children)
{
Console.WriteLine(string.Format("Child Id: {0}, Parent Id: {1}", child.Id, child.parent.Id ));
}
}
Console.ReadLine();
}
}
}
When using the DataContractSerializer, use the IsReference property of the DataContract
attribute to enable reference tracking when serializing and deserializing DataContracts.
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
[DataContract(IsReference = true)]
public class Child
{
[DataMember]
public Guid Id { get; set; }
[DataMember]
public Parent parent;
}
[DataContract(IsReference = true)]
public class Parent
{
[DataMember]
public Guid Id;
[DataMember]
public List<Child> Children;
}
class Program
{
static void Main(string[] args)
{
Child c1 = new Child { Id = Guid.NewGuid() };
Child c2 = new Child { Id = Guid.NewGuid() };
Parent p = new Parent { Id = Guid.NewGuid(), Children = new List<Child> { c1, c2 } };
c1.parent = p;
c2.parent = p;
using (var stream1 = new MemoryStream())
{
DataContractSerializer formatter = new DataContractSerializer(typeof(Parent));
formatter.WriteObject(stream1, p);
stream1.Position = 0;
var deserializedParent = formatter.ReadObject(stream1) as Parent;
foreach (var child in deserializedParent.Children)
{
Console.WriteLine("Child Id: {0}, Parent Id: {1}", child.Id, child.parent.Id);
}
}
Console.ReadLine();
}
}
回答2:
If you leave it alone and let Parent be a public read/write property of the Child class, .NET automatic serialization process will handle it properly.
回答3:
If the automatic deserialization doesn't work, you could have your Parent class implement the IDeserializationCallback interface, and update the children in the OnDeserialization method.
[Serializable]
class Parent : IDeserializationCallback
{
public List<child> children;
void IDeserializationCallback.OnDeserialization(Object sender)
{
if (null != children)
{
children.ForEach(c => c.parent = this);
}
}
}
回答4:
I accomplished this (sort of) by overriding the Add method in the child object's collection class, to 'set" a property value in the child class with the unique identifer of the parent object
public class Connections: List<Connection>
{ public new void Add(Connection connection)
{
connection.ApplicationName = ApplicationName;
base.Add(connection);
}
}
来源:https://stackoverflow.com/questions/353558/create-pointer-to-parent-object-in-deserialization-process-in-c-sharp