it-swarm.dev

Maneira correta de implementar IXmlSerializable?

Depois que um programador decide implementar IXmlSerializable, quais são as regras e práticas recomendadas para implementá-lo? Ouvi dizer que GetSchema() deve retornar null e ReadXml deve passar para o próximo elemento antes de retornar. Isso é verdade? E o WriteXml - ele deve escrever um elemento raiz para o objeto ou assume-se que a raiz já foi gravada? Como os objetos filhos devem ser tratados e gravados?

Aqui está uma amostra do que tenho agora. Vou atualizá-lo à medida que obtenho boas respostas.

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

XML de amostra correspondente

<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>
144
Greg

Sim, GetSchema () deve retornar nulo .

Método IXmlSerializable.GetSchema Este método está reservado e não deve ser usado. Ao implementar a interface IXmlSerializable, você deve retornar uma referência nula (Nada no Visual Basic) desse método e, em vez disso, se especificar um esquema personalizado é necessário, aplique XmlSchemaProviderAttribute à classe.

Para leitura e gravação, o elemento do objeto já foi gravado, portanto, você não precisa adicionar um elemento externo na gravação. Por exemplo, você pode apenas começar a ler/escrever atributos nos dois.

Para gravação :

A implementação WriteXml que você fornece deve gravar a representação XML do objeto. A estrutura grava um elemento wrapper e posiciona o gravador XML após seu início. Sua implementação pode escrever seu conteúdo, incluindo elementos filho. A estrutura então fecha o elemento wrapper.

E para leitura :

O método ReadXml deve reconstituir seu objeto usando as informações que foram gravadas pelo método WriteXml.

Quando esse método é chamado, o leitor é posicionado no início do elemento que agrupa as informações do seu tipo. Ou seja, logo antes da tag de início, que indica o início de um objeto serializado. Quando esse método retorna, ele deve ter lido o elemento inteiro do começo ao fim, incluindo todo o seu conteúdo. Diferentemente do método WriteXml, a estrutura não trata o elemento wrapper automaticamente. Sua implementação deve fazer isso. A não observação dessas regras de posicionamento pode fazer com que o código gere exceções inesperadas de tempo de execução ou corrompa dados.

Concordo que isso é um pouco incerto, mas tudo se resume a "é seu trabalho Read() a tag do elemento final do wrapper".

92
Marc Gravell

Escrevi um artigo sobre o assunto com exemplos, já que a documentação do MSDN ainda não está clara e os exemplos que você pode encontrar na Web são na maioria das vezes implementados incorretamente.

Armadilhas são manipulação de localidades e elementos vazios ao lado do que Marc Gravell já mencionou.

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

32
jdehaan

Sim, a coisa toda é meio que um campo minado, não é? A resposta de Marc Gravell praticamente cobre, mas eu gostaria de acrescentar que em um projeto em que trabalhei, achamos bastante estranho ter que escrever manualmente o elemento XML externo. Também resultou em nomes inconsistentes de elementos XML para objetos do mesmo tipo.

Nossa solução foi definir nossa própria interface IXmlSerializable, derivada da interface do sistema, que adicionou um método chamado WriteOuterXml(). Como você pode imaginar, esse método simplesmente escreveria o elemento externo, chamaria WriteXml() e, em seguida, escreveria o final do elemento. Obviamente, o serializador XML do sistema não chamaria esse método, portanto, só foi útil quando fizemos nossa própria serialização, para que possa ou não ser útil no seu caso. Da mesma forma, adicionamos um método ReadContentXml(), que não leu o elemento externo, apenas seu conteúdo.

8
EMP

Se você já possui uma representação XmlDocument da sua classe ou prefere a maneira XmlDocument de trabalhar com estruturas XML, uma maneira rápida e suja de implementar o IXmlSerializable é passar esse xmldoc para as várias funções.

AVISO: XmlDocument (e/ou XDocument) é uma ordem de magnitude mais lenta que xmlreader/writer, portanto, se o desempenho é um requisito absoluto, esta solução não é para você!

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
Thijs Dalhuijsen