Właściwy sposób wdrożenia IXmlSerializable?

Kiedy programista zdecyduje się wdrożyć IXmlSerializable, Jakie są zasady i najlepsze praktyki jego wdrażania? Słyszałem, że GetSchema() powinien zwrócić null i ReadXml powinien przejść do następnego elementu przed powrotem. Czy to prawda? A co z WriteXml - powinien napisać element root dla obiektu, czy też zakłada się, że root jest już napisany? Jak należy traktować i pisać przedmioty dziecka?

Oto próbka tego, co mam teraz. Zaktualizuję go, jak będę dobry odpowiedzi.
public class MyCalendar : IXmlSerializable
{
    private string _name;
    private bool _enabled;
    private Color _color;
    private List<MyEvent> _events = new List<MyEvent>();


    public XmlSchema GetSchema() { return null; }

    public void ReadXml(XmlReader reader)
    {
        if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyCalendar")
        {
            _name    = reader["Name"];
            _enabled = Boolean.Parse(reader["Enabled"]);
            _color   = Color.FromArgb(Int32.Parse(reader["Color"]));

            if (reader.ReadToDescendant("MyEvent"))
            {
                while (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyEvent")
                {
                    MyEvent evt = new MyEvent();
                    evt.ReadXml(reader);
                    _events.Add(evt);
                }
            }
            reader.Read();
        }
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("Name",    _name);
        writer.WriteAttributeString("Enabled", _enabled.ToString());
        writer.WriteAttributeString("Color",   _color.ToArgb().ToString());

        foreach (MyEvent evt in _events)
        {
            writer.WriteStartElement("MyEvent");
            evt.WriteXml(writer);
            writer.WriteEndElement();
        }
    }
}

public class MyEvent : IXmlSerializable
{
    private string _title;
    private DateTime _start;
    private DateTime _stop;


    public XmlSchema GetSchema() { return null; }

    public void ReadXml(XmlReader reader)
    {
        if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyEvent")
        {
            _title = reader["Title"];
            _start = DateTime.FromBinary(Int64.Parse(reader["Start"]));
            _stop  = DateTime.FromBinary(Int64.Parse(reader["Stop"]));
            reader.Read();
        }
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("Title", _title);
        writer.WriteAttributeString("Start", _start.ToBinary().ToString());
        writer.WriteAttributeString("Stop",  _stop.ToBinary().ToString());
    }
}

Odpowiedni przykład XML

<MyCalendar Name="Master Plan" Enabled="True" Color="-14069085">
    <MyEvent Title="Write Code" Start="-8589241828854775808" Stop="-8589241756854775808" />
    <MyEvent Title="???" Start="-8589241828854775808" Stop="-8589241756854775808" />
    <MyEvent Title="Profit!" Start="-8589247048854775808" Stop="-8589246976854775808" />
</MyCalendar>
Author: Greg, 2008-11-11

4 answers

Tak, GetSchema() powinna zwracać null.

IXmlSerializable.GetSchema Method This metoda jest zarezerwowana i nie powinna być używany. Przy realizacji IXmlSerializable interface, należy return a null reference (Nothing in Visual Basic) z tej metody, a zamiast, jeśli podanie schematu niestandardowego jest wymagane, zastosuj XmlSchemaProviderAttribute to the klasy.

Zarówno dla odczytu, jak i zapisu, element obiektu został już zapisany, więc nie musisz dodawać zewnętrznego elementu w write. Na przykład, możesz po prostu zacząć odczytywać/zapisywać atrybuty w tych dwóch.

Dla napisz :

Implementacja WriteXml provide powinien wypisać XML reprezentacja obiektu. Na framework pisze element wrappera i pozycjonuje XML writer po jego zaczynaj. Twoja realizacja może napisać jego zawartość, w tym dziecko żywioły. Ramy następnie zamyka owijarka element.

I dla czytane:

Metoda ReadXml musi odtworzyć Twój obiekt korzystając z informacji, które został napisany metodą WriteXml.

Po wywołaniu tej metody, czytelnik znajduje się na początku element, który zawija informacje dla w twoim typie. Czyli tuż przed znacznik start, który wskazuje początek serializowanego obiektu. Kiedy to metoda zwraca, musi odczytać cały element z beginning to end, łącznie z całą jego zawartością. W przeciwieństwie do metoda WriteXml, framework nie obsługuje elementu owijarki automatycznie. Twoja realizacja muszę to zrobić. Nie przestrzegając tych Zasady pozycjonowania mogą spowodować, że kod generowanie nieoczekiwanych WYJĄTKÓW środowiska wykonawczego lub uszkodzonych danych.

Zgadzam się, że jest to trochę niejasne, ale sprowadza się to do "twoim zadaniem jest Read() znacznik elementu końcowego opakowania".

 87
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-20 09:31:36

Napisałem jeden artykuł na ten temat z przykładami, ponieważ dokumentacja MSDN jest już dość niejasna, a przykłady, które można znaleźć w Internecie, są w większości przypadków nieprawidłowo zaimplementowane.

Pułapki są manipulacją lokalizacjami i pustymi elementami poza tym, o czym Marc Gravell już wspomniał.

Http://www.codeproject.com/KB/XML/ImplementIXmlSerializable.aspx

 31
Author: jdehaan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-27 09:36:57

Tak, cała sprawa to trochę pole minowe, prawda? Marc Gravell 's answer dość dużo obejmuje to, ale chciałbym dodać, że w projekcie, nad którym pracowałem, okazało się to dość kłopotliwe, aby ręcznie pisać zewnętrzny element XML. Spowodowało to również niespójne nazwy elementów XML dla obiektów tego samego typu.

Naszym rozwiązaniem było zdefiniowanie własnego interfejsu IXmlSerializable, wywodzącego się z systemu, który dodał metodę o nazwie WriteOuterXml(). Jak można się domyślić, ta metoda po prostu zapisuje zewnętrzny element, następnie wywołuje WriteXml(), a następnie zapisuje koniec elementu. Oczywiście system XML serializer nie wywołałby tej metody, więc była przydatna tylko wtedy, gdy zrobiliśmy własną serializację, więc może to być pomocne, ale nie musi być pomocne w Twoim przypadku. Podobnie dodaliśmy metodę ReadContentXml(), która nie odczytywała zewnętrznego elementu, tylko jego zawartość.

 8
Author: EMP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-19 03:48:18

Jeśli masz już reprezentację XmlDocument swojej klasy lub preferujesz sposób pracy XmlDocument ze strukturami XML, szybkim i brudnym sposobem implementacji IXmlSerializable jest po prostu przekazanie tego xmldoc do różnych funkcji.

Ostrzeżenie: XmlDocument (i/lub XDocument) jest o rząd wielkości wolniejszy niż XmlReader/writer, więc jeśli wydajność jest absolutnym wymogiem, To rozwiązanie nie jest dla Ciebie!

class ExampleBaseClass : IXmlSerializable { 
    public XmlDocument xmlDocument { get; set; }
    public XmlSchema GetSchema()
    {
        return null;
    }
    public void ReadXml(XmlReader reader)
    {
        xmlDocument.Load(reader);
    }

    public void WriteXml(XmlWriter writer)
    {
        xmlDocument.WriteTo(writer);
    }
}
 2
Author: Bob Verkouteren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-02 09:15:30