c# - Maneira correta de implementar o IXmlSerializable?



xml-serialization (5)

Quando um programador decide implementar o IXmlSerializable , quais são as regras e melhores práticas 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 sobre o WriteXml - ele deve escrever um elemento raiz para o objeto ou é assumido que a raiz já está escrita? Como os objetos filhos devem ser tratados e escritos?

Aqui está uma amostra do que tenho agora. Vou atualizá-lo enquanto recebo 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>

Answers

Sim, GetSchema () deve retornar null .

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

Tanto para leitura quanto para gravação, o elemento objeto já foi escrito, portanto, não é necessário incluir um elemento externo na gravação. Por exemplo, você pode simplesmente começar a ler / escrever atributos nos dois.

Para write :

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 gravar seu conteúdo, incluindo elementos filhos. A estrutura então fecha o elemento wrapper.

E para read :

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

Quando esse método é chamado, o leitor é posicionado no início do elemento que contém 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 todo o elemento do começo ao fim, incluindo todo o seu conteúdo. Ao contrário do método WriteXml, a estrutura não manipula o elemento wrapper automaticamente. Sua implementação deve fazer isso. Deixar de observar essas regras de posicionamento pode fazer com que o código gere exceções de tempo de execução inesperadas ou dados corrompidos.

Eu concordo que é um pouco claro, mas se resume a "é o seu trabalho para Read() a tag do elemento final do wrapper".


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

Nossa solução foi definir nossa própria interface IXmlSerializable , derivada do sistema one, que adicionava um método chamado WriteOuterXml() . Como você pode imaginar, este método simplesmente escreveria o elemento externo, então chamaria WriteXml() , e então gravaria o final do elemento. É claro que o serializador XML do sistema não chamaria esse método, portanto, isso só era útil quando fizemos nossa própria serialização, de modo que pode 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.


Se você já tem uma representação XmlDocument de sua classe ou prefere a maneira XmlDocument de trabalhar com estruturas XML, uma maneira rápida e suja de implementar IXmlSerializable é apenas passar este 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 for 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);
    }
}

Eu escrevi um artigo sobre o assunto com amostras, pois 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 locais e elementos vazios ao lado do que Marc Gravell já mencionou.

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


Além dos outros motivos já mencionados, essa é a situação na qual uma classe está implementando duas interfaces diferentes que possuem uma propriedade / método com o mesmo nome e assinatura.

/// <summary>
/// This is a Book
/// </summary>
interface IBook
{
    string Title { get; }
    string ISBN { get; }
}

/// <summary>
/// This is a Person
/// </summary>
interface IPerson
{
    string Title { get; }
    string Forename { get; }
    string Surname { get; }
}

/// <summary>
/// This is some freaky book-person.
/// </summary>
class Class1 : IBook, IPerson
{
    /// <summary>
    /// This method is shared by both Book and Person
    /// </summary>
    public string Title
    {
        get
        {
            string personTitle = "Mr";
            string bookTitle = "The Hitchhikers Guide to the Galaxy";

            // What do we do here?
            return null;
        }
    }

    #region IPerson Members

    public string Forename
    {
        get { return "Lee"; }
    }

    public string Surname
    {
        get { return "Oades"; }
    }

    #endregion

    #region IBook Members

    public string ISBN
    {
        get { return "1-904048-46-3"; }
    }

    #endregion
}

Este código compila e executa OK, mas a propriedade Title é compartilhada.

Claramente, gostaríamos que o valor do Título retornado dependesse de estarmos tratando a Classe 1 como um Livro ou uma Pessoa. É quando podemos usar a interface explícita.

string IBook.Title
{
    get
    {
        return "The Hitchhikers Guide to the Galaxy";
    }
}

string IPerson.Title
{
    get
    {
        return "Mr";
    }
}

public string Title
{
    get { return "Still shared"; }
}

Observe que as definições explícitas da interface são inferidas como sendo públicas - e, portanto, você não pode declará-las como públicas (ou de outra forma) explicitamente.

Note também que você ainda pode ter uma versão "compartilhada" (como mostrado acima), mas enquanto isso é possível, a existência de tal propriedade é questionável. Talvez ele possa ser usado como uma implementação padrão do Title - para que o código existente não precise ser modificado para converter Class1 em IBook ou IPerson.

Se você não definir o título "compartilhado" (implícito), os consumidores de Class1 deverão converter explicitamente as instâncias de Class1 para IBook ou IPerson. Caso contrário, o código não será compilado.





c# xml xml-serialization