Сериализация списка <>, экспортированного как ICollection <> в XML - PullRequest
7 голосов
/ 20 сентября 2011

У меня есть приложение на C # .NET 3.5, где я хотел бы сериализовать класс, содержащий List<> в XML. Мой класс выглядит так:

[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_; }
    }
}

Если я напишу это так:

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);

А затем сериализовать это так:

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);
}

Я получаю файл, который выглядит так:

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

Но мне нужен XML, который выглядит следующим образом:

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

Что мне нужно сделать, чтобы List<> появился в XML?

Ответы [ 2 ]

3 голосов
/ 30 сентября 2011

При правильном ответе нет смысла создавать некрасивый установщик для открытого свойства List<T>, чтобы выдавать исключение.

Это потому, что List<> уже реализует ICollection<T> и предоставляет метод с подписью void Add(T object), который используется механизмом сериализации;

Вам нужно только добавить установщик к сериализуемым общедоступным свойствам и изменить ICollection<T> на 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_; } }
}

Вы получите вывод:

<?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>

Кроме того, лучше сериализовать XML-данные в памяти, чтобы увидеть результаты или протестировать их следующим образом:

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);
}

Для сериализации с использованием интерфейсов используйте мой проект XmlSerialization

3 голосов
/ 20 сентября 2011

XmlSerializer требует, чтобы сериализуемые свойства имели установщик.Кроме того, XmlSerializer не может сериализовать свойства интерфейса.Будет работать следующий код:

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

Если вам не нравится это решение (исключение довольно уродливое), тогда вы можете реализовать IXmlSerializable и написать свою собственную сериализацию.

Редактировать: Артур Мустафин прав, члены, которые реализуют IEnumerable или ICollection , не нуждаются в установщике , как объяснено в thismsdn page :

XmlSerializer предоставляет особый режим для классов, которые реализуют IEnumerable или ICollection.Класс, реализующий IEnumerable, должен реализовывать открытый метод Add, который принимает один параметр.Параметр метода Add должен иметь тот же тип, который возвращается из свойства Current для значения, возвращаемого из GetEnumerator, или одной из основ этого типа.Класс, который реализует ICollection (такой как CollectionBase) в дополнение к IEnumerable, должен иметь открытое индексированное свойство Item (индексатор в C #), которое принимает целое число, и у него должно быть открытое свойство Count типа integer,Параметр метода Add должен быть того же типа, который возвращается из свойства Item или одной из основ этого типа.Для классов, которые реализуют ICollection, значения для сериализации извлекаются из индексированного свойства Item, а не путем вызова GetEnumerator.

...