views:

50

answers:

3

I am receiving the following exception when trying to serialize an object using XMLSerialization.

A circular reference was detected while serializing an object of type MyObject}

I know the circular reference is because ObjectA can have a childObject of ObjectB and ObjectB's parentObject is ObjectA, however I would like to keep that reference if possible . Is there a way to get this object to serialize with XML Serialization without losing any data during the serialization process? I'm not very familar with serialization so I'm hoping theres some kind of Attribute I could set.

A: 

Mark the parentObject property as [NonSerialized].

http://blog.kowalczyk.info/article/Serialization-in-C.html

pjabbott
If I mark it NonSerialized then the data is gone once I deserialize the object.
Rachel
NonSerialized isn't used by XmlSerializer
Marc Gravell
+1  A: 

If you can use the DataContractSerializer instead of the XMLSerializer then you can use the IsReference property on the DataContract attribute. Enabling this will keep the references, so that they will be recreated upon deserialization.

The DataContractSerializer also serializes to XML, but you have somewhat less control over what the output looks like, that you do with the older XMLSerializer. You can read more about the serializers here: http://www.danrigsby.com/blog/index.php/2008/03/07/xmlserializer-vs-datacontractserializer-serialization-in-wcf/

AHM
Thank you, I will look into that. Do you have an example of how I would set that value on a class that just as a `[Serializable]` attribute instead of being defined as a `[DataContract]`?
Rachel
Ugh trying to find a way to do this without switching to `[DataContract]` attributes.... My base class is used for everything and if I modify that, I have to modify almost all the classes in my ObjectLibrary to use `[DataContract]` instead of `[Serializable]` and add the `[DataMember]` attribute to all properties and have no clue how to do that fast...
Rachel
You can't do this without switching to DataContract. It's just not supported. If you want to use the Serializable attribute, then you have to do something like Sergey suggests, and call some code to fix the references after deserialization.
AHM
Thank you. I ended up going with Sergey's solution since I didn't want to rewrite all my classes to use DataContract/DataMember attributes, but +1 anyways because it would have been a good solution if I didn't have so many classes to modify.
Rachel
Actually I missed that BinaryFormatter and DataContractSerializer cope with circular references without assistance. So all this logic with OnSerializedAttribute is reduntant.
Sergey Teplyakov
+1  A: 

There are several options depending on serializer type.

If you could use DataContractSerializer or BinaryFormatter then you may use OnSerializedAttribute and set Parent property for your child object to this:

[Serializable]
public class Child
{
    public string Foo { get; set; }

    public Parent Parent { get { return parent; } set { parent = value; } }

    // We don't want to serialize this property explicitly.
    // But we could set it during parent deserialization
    [NonSerialized]
    private Parent parent;
}

[Serializable]
public class Parent
{
    // BinaryFormatter or DataContractSerializer whould call this method
    // during deserialization
    [OnDeserialized()]
    internal void OnSerializedMethod(StreamingContext context)
    {
        // Setting this as parent property for Child object
        Child.Parent = this;
    }

    public string Boo { get; set; }

    public Child Child { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Child c = new Child { Foo = "Foo" };
        Parent p = new Parent { Boo = "Boo", Child = c };

        using (var stream1 = new MemoryStream())
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof (Parent));
            serializer.WriteObject(stream1, p);
            stream1.Position = 0;
            var p2 = (Parent)serializer.ReadObject(stream1);

            Console.WriteLine(object.ReferenceEquals(p, p2)); //return false
            Console.WriteLine(p2.Boo); //Prints "Boo"

            //Prints: Is Parent not null: True
            Console.WriteLine("Is Parent not null: {0}", p2.Child.Parent != null);
        }
    }

}

If you want to use XmlSerializer you should implement IXmlSerializable, use XmlIgnoreAttribute and implemented more or less the same logic in ReadXml method. But in this case you should also implement all Xml serialization logic manually:

[Serializable]
public class Child
{
    public Child()
    {
    }

    public string Foo { get; set; }

    [XmlIgnore]
    public Parent Parent { get; set; }
}

[Serializable]
public class Parent
{
    public Parent()
    {
    }

    #region IXmlSerializable Members

    public System.Xml.Schema.XmlSchema GetSchema()
    {
        throw new NotImplementedException();
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        //Reading Parent content
        //Reading Child
        Child.Parent = this;
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        //Writing Parent and Child content
    }

    #endregion

    public string Boo { get; set; }

    public Child Child { get; set; }
}
Sergey Teplyakov
If I do that then the reference is gone when I deserialize the object. Object is coming from a WCF service
Rachel
Thank you! Your edit clarified things... I totally forgot I could add the Parent `[OnDeserializing()]`, which is what I ended up doing.
Rachel
As I mentioned with AHM's answers comment, OnDeserializingAttribute is redundant in this case, because it still works fine without it. But you still could add some additional logic in this method.
Sergey Teplyakov