Cara yang tepat untuk mengimplementasikan IXmlSerializable?


153

Setelah seorang programmer memutuskan untuk mengimplementasikan IXmlSerializable, apa aturan dan praktik terbaik untuk mengimplementasikannya? Saya pernah mendengar bahwa GetSchema()harus kembali nulldan ReadXmlharus pindah ke elemen berikutnya sebelum kembali. Apakah ini benar? Dan bagaimana WriteXml- haruskah menulis elemen root untuk objek atau diasumsikan bahwa root sudah ditulis? Bagaimana seharusnya benda-benda anak diperlakukan dan ditulis?

Ini contoh dari apa yang saya miliki sekarang. Saya akan memperbaruinya karena saya mendapat respons yang baik.

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 Sampel yang Sesuai

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

3
Bisakah Anda menambahkan sampel xml ke pertanyaan ini? Itu akan membuatnya lebih mudah untuk membaca bersama dengan kode. Terima kasih!
Rory

Bagaimana dengan menangani kasus di mana ada komentar XML dll setelah Acara terakhir di xml Anda. yaitu apakah Anda harus menyelesaikan metode ReadXml () dengan sesuatu yang memeriksa bahwa Anda membaca hingga elemen akhir? Saat ini mengasumsikan Baca terakhir () melakukan itu tetapi mungkin tidak selalu.
Rory

7
@Rory - Sampel ditambahkan. Lebih baik terlambat daripada tidak sama sekali?
Greg

@Greg Info bagus. Tidakkah Anda juga ingin memiliki ReadXml dan WriteXml menggunakan Budaya Invarian? Saya pikir Anda mungkin mengalami masalah jika pengguna pindah ke negara lain dan mengubah Pengaturan Wilayah dan Bahasa mereka. Jika demikian, kode ini mungkin tidak deserialisasi dengan benar. Saya telah membaca bahwa ini adalah praktik terbaik untuk selalu menggunakan Budaya Invariant ketika melakukan serialisasi
nirkabel publik

Jawaban:


100

Ya, GetSchema () harus mengembalikan nol .

Metode IXmlSerializable.GetSchema Metode ini dicadangkan dan tidak boleh digunakan. Saat menerapkan antarmuka IXmlSerializable, Anda harus mengembalikan referensi nol (Tidak Ada dalam Visual Basic) dari metode ini, dan sebaliknya, jika menentukan skema khusus diperlukan, terapkan XmlSchemaProviderAttribute ke kelas.

Untuk membaca dan menulis, elemen objek sudah ditulis, jadi Anda tidak perlu menambahkan elemen luar dalam tulis. Misalnya, Anda bisa mulai membaca / menulis atribut di keduanya.

Untuk menulis :

Implementasi WriteXml yang Anda berikan harus menulis representasi XML objek. Kerangka kerja menulis elemen pembungkus dan posisi penulis XML setelah dimulai. Implementasi Anda dapat menulis kontennya, termasuk elemen anak. Kerangka kerja kemudian menutup elemen pembungkus.

Dan untuk dibaca :

Metode ReadXml harus menyusun kembali objek Anda menggunakan informasi yang ditulis oleh metode WriteXml.

Ketika metode ini dipanggil, pembaca diposisikan di awal elemen yang membungkus informasi untuk tipe Anda. Yaitu, tepat sebelum tag awal yang menunjukkan awal objek serial. Ketika metode ini kembali, ia harus membaca seluruh elemen dari awal hingga akhir, termasuk semua kontennya. Berbeda dengan metode WriteXml, kerangka kerja tidak menangani elemen pembungkus secara otomatis. Implementasi Anda harus melakukannya. Gagal mematuhi aturan pemosisian ini dapat menyebabkan kode menghasilkan pengecualian runtime yang tidak terduga atau data yang rusak.

Saya akan setuju bahwa ini sedikit tidak jelas, tetapi intinya adalah "itu adalah tugas Anda untuk Read()tag elemen akhir pembungkus".


Bagaimana dengan menulis dan membaca elemen acara? Rasanya meretas untuk menulis elemen awal secara manual. Saya pikir saya telah melihat seseorang menggunakan XmlSerializer dalam metode menulis untuk menulis setiap elemen anak.
Greg

@Reg; baik penggunaannya baik-baik saja ... ya, Anda dapat menggunakan XmlSerializer bersarang jika Anda membutuhkannya, tetapi itu bukan satu-satunya pilihan.
Marc Gravell

3
Terima kasih untuk tindakan pencegahan ini, kode sampel di dalam MSDN cukup tidak berguna dan tidak jelas mengenai hal ini. Saya terjebak berkali-kali dan bertanya-tanya tentang perilaku asimetris dari Baca / WriteXml.
jdehaan

1
@MarcGravell Saya tahu ini adalah utas lama. "Kerangka kerja menulis elemen pembungkus dan memposisikan penulis XML setelah dimulai." Di sinilah saya berjuang. Apakah ada cara untuk memaksa kerangka melewati langkah ini secara otomatis menangani pembungkus? Saya memiliki situasi di mana saya harus melewati langkah ini: stackoverflow.com/questions/20885455/…
James

@James bukan yang terbaik dari pengetahuan saya
Marc Gravell

34

Saya menulis satu artikel tentang subjek dengan sampel sebagai dokumentasi MSDN sekarang cukup jelas dan contoh-contoh yang Anda temukan di web sebagian besar waktu salah diimplementasikan.

Perangkap adalah penanganan lokal dan elemen kosong di samping apa yang telah disebutkan Marc Gravell.

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


Artikel yang luar biasa! Saya pasti akan merujuknya lain kali saya ingin membuat serial beberapa data.
Greg

Terima kasih! jumlah umpan balik positif memberi imbalan jumlah waktu yang diinvestasikan untuk menulisnya. Saya sangat menghargai Anda menyukainya! Jangan ragu untuk meminta kritik beberapa poin.
jdehaan

Contohnya jauh lebih berguna daripada mengutip MSDN.

Terima kasih untuk proyek codep, saya akan memilih itu juga, jika saya bisa. Hal-hal pada atribut benar-benar komprehensif dibandingkan dengan MSDN. Sebagai contoh, kelas saya: IXMLSerializable pecah ketika diawali oleh xsd.exe yang dihasilkan [Serializable (), XmlType (Namespace = "MonitorService")].
John

8

Ya, semuanya sedikit seperti ladang ranjau, bukan? Jawaban Marc Gravell cukup banyak membahasnya, tetapi saya ingin menambahkan bahwa dalam proyek yang saya kerjakan kami merasa agak canggung harus secara manual menulis elemen XML luar. Ini juga menghasilkan nama elemen XML yang tidak konsisten untuk objek dengan tipe yang sama.

Solusi kami adalah mendefinisikan IXmlSerializableantarmuka kami sendiri , yang berasal dari sistem, yang menambahkan metode yang disebut WriteOuterXml(). Seperti yang bisa Anda tebak, metode ini hanya akan menulis elemen luar, lalu memanggil WriteXml(), lalu menulis akhir elemen. Tentu saja, serializer sistem XML tidak akan memanggil metode ini, jadi itu hanya berguna ketika kami melakukan serialisasi sendiri, sehingga mungkin atau mungkin tidak membantu dalam kasus Anda. Demikian pula, kami menambahkan ReadContentXml()metode, yang tidak membaca elemen luar, hanya isinya.


5
Dengan C # 3.0 Anda mungkin dapat melakukan ini dengan menulis metode ekstensi sebagai gantinya, tetapi ide yang menarik.
Marc Gravell

2

Jika Anda sudah memiliki representasi XmlDocument dari kelas Anda atau lebih suka cara XmlDocument bekerja dengan struktur XML, cara cepat dan kotor mengimplementasikan IXmlSerializable adalah dengan hanya meneruskan xmldoc ini ke berbagai fungsi.

PERINGATAN: XmlDocument (dan / atau XDocument) adalah urutan besarnya lebih lambat dari xmlreader / penulis, jadi jika kinerja merupakan persyaratan mutlak, solusi ini bukan untuk Anda!

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

0

Implementasi antarmuka tercakup oleh jawaban lain, tetapi saya ingin melemparkan 2-sen saya untuk elemen root.

Saya telah belajar di masa lalu untuk lebih suka menempatkan elemen root sebagai metadata. Ini memiliki beberapa manfaat:

  • Jika ada objek nol, itu masih bisa bersambung
  • Dari sudut pandang keterbacaan kode, masuk akal

Di bawah ini adalah contoh dari kamus berseri serial di mana elemen root kamus didefinisikan dengan cara itu:

using System.Collections.Generic;

[System.Xml.Serialization.XmlRoot("dictionary")]
public partial class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, System.Xml.Serialization.IXmlSerializable
{
            public virtual System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public virtual void ReadXml(System.Xml.XmlReader reader)
    {
        var keySerializer = new System.Xml.Serialization.XmlSerializer(typeof(TKey));
        var valueSerializer = new System.Xml.Serialization.XmlSerializer(typeof(TValue));
        bool wasEmpty = reader.IsEmptyElement;
        reader.Read();
        if (wasEmpty)
            return;
        while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
        {
            reader.ReadStartElement("item");
            reader.ReadStartElement("key");
            TKey key = (TKey)keySerializer.Deserialize(reader);
            reader.ReadEndElement();
            reader.ReadStartElement("value");
            TValue value = (TValue)valueSerializer.Deserialize(reader);
            reader.ReadEndElement();
            Add(key, value);
            reader.ReadEndElement();
            reader.MoveToContent();
        }

        reader.ReadEndElement();
    }

    public virtual void WriteXml(System.Xml.XmlWriter writer)
    {
        var keySerializer = new System.Xml.Serialization.XmlSerializer(typeof(TKey));
        var valueSerializer = new System.Xml.Serialization.XmlSerializer(typeof(TValue));
        foreach (TKey key in Keys)
        {
            writer.WriteStartElement("item");
            writer.WriteStartElement("key");
            keySerializer.Serialize(writer, key);
            writer.WriteEndElement();
            writer.WriteStartElement("value");
            var value = this[key];
            valueSerializer.Serialize(writer, value);
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
    }

    public SerializableDictionary() : base()
    {
    }

    public SerializableDictionary(IDictionary<TKey, TValue> dictionary) : base(dictionary)
    {
    }

    public SerializableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) : base(dictionary, comparer)
    {
    }

    public SerializableDictionary(IEqualityComparer<TKey> comparer) : base(comparer)
    {
    }

    public SerializableDictionary(int capacity) : base(capacity)
    {
    }

    public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer) : base(capacity, comparer)
    {
    }

}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.