9

I have a C# .NET 3.5 application where I would like to serialize a class containing a List<> to XML. My class looks like this:

[XmlRoot("Foo")]
class Foo
{
    private List<Bar> bar_ = new List<Bar>();

    private string something_ = "My String";

    [XmlElement("Something")]
    public string Something { get { return something_; } }

    [XmlElement("Bar")]
    public ICollection<Bar> Bars
    {
        get { return bar_; }
    }
}

If I populate it like this:

Bar b1 = new Bar();
// populate b1 with interesting data
Bar b2 = new Bar();
// populate b2 with interesting data

Foo f = new Foo();
f.Bars.Add(b1);
f.Bars.Add(b2);

And then serialize it like this:

using (System.IO.TextWriter textWriter = new System.IO.StreamWriter(@"C:\foo.xml"))
{
    System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(Foo));
    serializer.Serialize(textWriter, f);
}

I get a file that looks like this:

<Foo>
    <Something>My String</Something>
</Foo>

But, what I want is XML that looks like this:

<Foo>
    <Something>My String</Something>
    <Bar>
        <!-- Data from first Bar -->
    </Bar>
    <Bar>
        <!-- Data from second Bar -->
    </Bar>
</Foo>

What do I need to do to get the List<> to appear in the XML?

PaulH
  • 7,759
  • 8
  • 66
  • 143
  • I don't believe you can `XmlSerialize` an interface. Why do you want to serialize as `ICollection` anyway? Serialize as `List` and return to the consumer an `ICollection` ...??? – IAbstract Sep 20 '11 at 19:05
  • @IAbstract - I'm not sure I understand. Do you mean to mark the `private List bar_` with the `[XmlElement("Bar")]` tag? That does not change the output. Also, the `XmlSerializer` documentation suggests that it does work with both `IEnumerable` and `ICollection` interfaces. http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=VS.90%29.aspx – PaulH Sep 20 '11 at 19:23
  • I think IAbstract has it - you can't serialize an interface. So instead you should change Foo so that Bars is a List, not an ICollection – Matt Roberts Sep 20 '11 at 19:26
  • It seems wrong to return a concrete List<> from an accessor. Even FX-COP will complain. http://msdn.microsoft.com/en-us/library/ms182327%28v=VS.90%29.aspx – PaulH Sep 20 '11 at 19:34
  • Oh. It's a known issue: https://connect.microsoft.com/VisualStudio/feedback/details/528310/xmlserializer-fails-to-deserialize-readonly-members-that-are-collections-if-they-have-a-private-setter – PaulH Sep 20 '11 at 19:34
  • Use my interface driven serialization: http://xmlserialization.codeplex.com – Alan Turing Sep 30 '11 at 01:39

2 Answers2

4

Giving a correct answer, there is no point to create ugly setter to the List<T> public property, to throw an exception.

This is because List<> is already implements ICollection<T> and provides method with signature void Add(T object) which is used by Serialization mechanism;

You are only have to add the setter to the public properties being serialized, and change ICollection<T> to List<T>:

[XmlRoot("Foo")]
public class Foo
{
    private List<Bar> bar_ = new List<Bar>();

    [XmlElement("Something")]
    public string Something { get; set; }

    [XmlElement("Bar")]
    public List<Bar> Bars { get { return bar_; } }
}

You will get an output:

<?xml version="1.0" encoding="utf-8"?>
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Something>My String</Something>
  <Bar />
  <Bar />
</Foo>

Also, it is a better idea to serialize xml in-memory, to see the results, or test it, as follows:

static void Main(string[] args)
{
     Bar b1 = new Bar();
     // populate b1 with interesting data
     Bar b2 = new Bar();
     // populate b2 with interesting data

     Foo f = new Foo();
     f.Bars.Add(b1);
     f.Bars.Add(b2);
     f.Something = "My String";

     using (MemoryStream ms = new MemoryStream())
     using (System.IO.TextWriter textWriter = new System.IO.StreamWriter(ms))
     {
         System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(Foo));
         serializer.Serialize(textWriter, f);
         string text = Encoding.UTF8.GetString(ms.ToArray());
         Console.WriteLine(text);
     }

    Console.ReadKey(false);
}

To serialize using interfaces, use my project XmlSerialization

David Gardiner
  • 16,892
  • 20
  • 80
  • 117
Alan Turing
  • 2,482
  • 17
  • 20
  • Just to be sure, are you saying it is "a better idea to serialize xml in-memory" all the time in case you would like to see what is going on or were you simply proposing a way for the OP to test more easily? I suppose it is the second option but if it is the first one, could you elaborate please? – bkqc Jul 30 '19 at 20:53
3

The XmlSerializer requires that serializable properties have a setter. Besides that, the XmlSerializer can not serialize interface properties. The following code will work:

[XmlElement("Bar")]
public List<Bar> Bars
{
    get { return bar_; }
    set { throw new NotSupportedException("This property 'Bars' cannot be set. This property is readonly."); }
}

If you don't like this solution (the exception is kinda ugly) then you could implement IXmlSerializable and write your own custom serialization.

Edit: Artur Mustafin is right, members that implement IEnumerable or ICollection don't need a setter, as is explained on this msdn page:

The XmlSerializer gives special treatment to classes that implement IEnumerable or ICollection. A class that implements IEnumerable must implement a public Add method that takes a single parameter. The Add method's parameter must be of the same type as is returned from the Current property on the value returned from GetEnumerator, or one of that type's bases. A class that implements ICollection (such as CollectionBase) in addition to IEnumerable must have a public Item indexed property (indexer in C#) that takes an integer, and it must have a public Count property of type integer. The parameter to the Add method must be the same type as is returned from the Item property, or one of that type's bases. For classes that implement ICollection, values to be serialized are retrieved from the indexed Item property, not by calling GetEnumerator.

David Gardiner
  • 16,892
  • 20
  • 80
  • 117
Elian Ebbing
  • 18,779
  • 5
  • 48
  • 56