views:

888

answers:

3

Dear ladies and sirs.

Observe the following sample code:

namespace A
{
  [Serializable]
  internal class ComplexObject<T> : List<T>, IEquatable<ComplexObject<T>>
    where T : IEquatable<T>
  {
    private T m_state;

    internal T State
    {
      get { return m_state; }
      set { m_state = value; }
    }

    public bool Equals(ComplexObject<T> other)
    {
      // Implementation is omitted to save space.
    }
  }

  public static class Program
  {
    public static void Main()
    {
      var obj = new ComplexObject<int>();
      obj.State = 100;
      var stream = new MemoryStream();
      var serializer = new DataContractSerializer(obj.GetType());
      serializer.WriteObject(stream, obj);
      stream.Flush();
      stream.Seek(0, SeekOrigin.Begin);
      var copy = (ComplexObject<int>)serializer.ReadObject(stream);
      Debug.Assert(obj.Equals(copy));
    }
  }
}

Note that ComplexObject<T> derives from List<T>.

Anyway, the last assertion fails. Replacing [Serializable] with [CollectionDataContract] and attaching [DataMember] to m_state yields the same negative result.

It is as though the DataContractSerializer notices that the class is a collection and chooses to ignore its other state.

Please advice anyone how to solve this issue given that:

  • I would like to make as few changes to ComplexObject<T> as possible
  • I am stuck with DataContractSerializer for reasons irrelevant for this question

Thanks a lot in advance.

EDIT:

public bool Equals(ComplexObject<T> other)
{
  if (!m_state.Equals(other.m_state) || Count != other.Count)
  {
    return false;
  }

  bool result = true;
  for (int i = 0; i < Count && (result = this[i].Equals(other[i])); ++i)
  {
  }
  return result;
}
A: 

Here is some code I have for cloning or serializing an object. I would be curious to see if you have the same issues with this. This code simply returns an Object type, but you can cast the result to your object type.

var serializer = new System.Runtime.Serialization.DataContractSerializer(GetType());
using (var ms = new System.IO.MemoryStream())
{
   serializer.WriteObject(ms, this);
   ms.Position = 0;
   return serializer.ReadObject(ms);
}
Randy Minder
Exactly the same problem. BTW, I do not see how your code is different.
mark
+2  A: 

To correctly serialize a List structure, you have to use the CollectionDataContract attribute like so:

 [CollectionDataContract]
 [Serializable]
 internal class ComplexObject<T> : List<T>, IEquatable<ComplexObject<T>>
    where T : IEquatable<T>

However, the CollectionDataContract doesn't allow for additional DataMembers to be serialized. The workaround would be to avoid inheriting from the list, but make it a member variable instead and optionally implement the ICollection, like so:

[DataContract]
[Serializable]
internal class ComplexObject<T> : ICollection<T>, IEquatable<ComplexObject<T>>
  where T : IEquatable<T> 
{
    private T m_state;

    [DataMember]
    public T State
    {
        get { return m_state; }
        set { m_state = value; }
    }

    private List<T> m_List = new List<T>();

    [DataMember]
    public List<T> List
    {
        get { return m_List; }
        set { m_List = value; }
    }

    public bool Equals(ComplexObject<T> other)
    {
        if (!other.State.Equals(State))
            return false;

        if (other.List.Count != List.Count)
            return false;

        for (int i = 0; i < other.List.Count;i++)
        {
            if (!other.List[i].Equals(List[i]))
                return false;
        }

        return true;
    }

    // ICollection members left out to save space

    // helper methods to wrap around the List to decrease the amount
    // of refactoring work you would have to do
    public void Add(T item)
    {
        List.Add(item);
    }

    public bool Remove(T item)
    {
        return List.Remove(item);
    }

    public T this[int index]
    {
        get { return List[index]; }
    }
}
enderminh
Thanks, but I have noticed that serializing a List<T> derived objects ignores the other state - that is the essence of the question! I would like to know whether this is a well known issue. Aggregating the list instead of inheriting it is a big change, I want to be absolutely sure that there is simply no other way.
mark
Yeah, that's a known issue. The problem is that .NET treats the List<T> as an array when it serializes it out, and you therefore can't add any other member to the "XML array". Supposedly with .NET 4.0 you can serialize any POCO that is not even marked as serializable. Maybe that might help you.
enderminh
Thanks, for the reply. Do you happen to know where can I read about it in the official microsoft documentation?
mark
Actually, I was wrong. The ability to serialize POCOs is actually already available with .NET 3.5 SP1. See http://msdn.microsoft.com/en-us/library/dd203052.aspx and http://www.pluralsight.com/community/blogs/aaron/archive/2008/05/13/50934.aspx. Try that approach.
enderminh
A: 

The problem is when you are trying to return an array of your object - at least it was for me.

I figure out that I needed to create a List of the type of my object class, add that list to the DataContractSerrializer(typeof(mylist));

thus;

List<LinqtoSQLTableClass> mylist = new List<LinqtoSQLTableClass>();
DataContractSerializer(mylist.GetType());
StringBuilder sb = new StringBuilder();

var query = linqtosql blah blah

XmlWriter writer = XmlWriter.Create(sb);
            dcs.WriteObject(writer, query);        
            writer.Close();
KenL