I've been banging my head against this issue for the last day, and I finally narrowed it down to a simple test case.
(BTW, I'm using the Nov CTP of Whidbey and .NET 2.0 for this code.)
While sending SOAP messages between my Web services, I'd been using XML serialization to pass CLR objects (DTOs). When trying to send a specific type of object, the serialization barfed with this exception:
System.InvalidOperationException: There was an error generating the XML document
. ---> System.InvalidOperationException: A circular reference was detected while serializing an object of type TestXmlSerializer.MyObject.
I could verify that there definitely wasn't a simple circular reference of object A referencing object B, and vice versa. However, it did have something to do with object A owning a collection to (or keeping a reference to) object B. Object B didn't reference object A, though.
I first thought it was an issue with object A and B having common base classes, but that wasn't it.
Finally, i narrowed it down to an issue with the base class supporting an object ID of type System.Guid. I'd overridden Equals(object o) to state that objects were equal when their object IDs were equal.
Unfortunately, but understandably, it looks like the XML serializer uses Object.Equals to find circular references in any object graph. So, if there were initialized objects that had the empty GUID as the object ID, it would consider them the same object and throw this exception.
I still have to review my code to see the effect of removing the overridden Equals operator. I'm keeping all object instances in generic List<T> collections. I probably just need to implement a different method to test object equivalence, if i need it in my methods.
See the code sample below... the first object instance 'dd' throws the exception, but if i assign a valid GUID to each object, the second instance of the object 'dd' serializes properly.
Code Sample
#region Using directives
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Globalization;
#endregion
namespace TestXmlSerializer
{
public abstract class Base
{
private Guid _objectId = Guid.Empty;
public Guid ObjectId
{
get
{
return _objectId;
}
set
{
_objectId = value;
}
}
public Base()
{
}
public override bool Equals(object obj)
{
Base ro = obj as Base;
if (ro != null)
return (ro.ObjectId == _objectId);
else
return false;
}
public override int GetHashCode()
{
return _objectId.GetHashCode();
}
}
public class MyObject : Base
{
private string _name = null;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public MyObject()
{
}
}
public class D : Base
{
public List<Base> Objects = new List<Base>();
private string _name = null;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public D()
{
}
}
class Program
{
public static string ToXmlString(object o, Type[] extraTypes)
{
if (o == null)
return null;
StringBuilder sb = new StringBuilder();
try
{
XmlSerializer x = null;
StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture);
XmlWriterSettings xmlSettings = new XmlWriterSettings();
xmlSettings.OmitXmlDeclaration = true;
xmlSettings.ConformanceLevel = ConformanceLevel.Document;
xmlSettings.Indent = true;
XmlWriter xmlWriter = XmlTextWriter.Create(sw, xmlSettings);
if (extraTypes != null)
x = new XmlSerializer(o.GetType(), extraTypes);
else
x = new XmlSerializer(o.GetType());
x.Serialize(xmlWriter, o);
xmlWriter.Flush();
xmlWriter.Close();
sw.Flush();
sw.Close();
}
catch (Exception e)
{
Console.WriteLine("Failed serializing to XML.");
Console.WriteLine(e.ToString());
}
return sb.ToString();
}
static void Main(string[] args)
{
D dd = new D();
dd.Name = "foo";
MyObject myo = new MyObject();
myo.Name = "baz";
dd.Objects.Add(myo);
Console.WriteLine("Serializing D....");
Console.WriteLine(ToXmlString(dd, new Type[] { typeof(MyObject), typeof(Base) }));
Console.WriteLine("... done.");
Console.WriteLine();
dd = new D();
dd.Name = "foo";
dd.ObjectId = Guid.NewGuid();
myo = new MyObject();
myo.Name = "baz";
myo.ObjectId = Guid.NewGuid();
dd.Objects.Add(myo);
Console.WriteLine("Serializing D (unique)....");
Console.WriteLine(ToXmlString(dd, new Type[] { typeof(MyObject), typeof(Base) }));
Console.WriteLine("... done.");
Console.WriteLine();
Console.WriteLine("Press <enter> to continue.");
Console.ReadLine();
}
}
}