IXmlSerializable uygulamak için doğru yolu?


153

Bir programcı uygulamaya karar verdiğinde IXmlSerializable, onu uygulamak için kurallar ve en iyi uygulamalar nelerdir? Bunun GetSchema()geri dönmesi gerektiğini nullve ReadXmlgeri dönmeden önce bir sonraki öğeye geçmesi gerektiğini duydum . Bu doğru mu? Peki ya WriteXml- nesne için bir kök eleman yazmalı mı yoksa kökün zaten yazıldığı var mı? Alt nesneler nasıl tedavi edilmeli ve yazılmalıdır?

İşte şimdi sahip olduğum şeyin bir örneği. İyi yanıtlar alırken güncelleyeceğim.

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

Karşılık gelen Örnek 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>

3
Bu soruya xml örneği ekleyebilir misiniz? Kodla birlikte okumayı kolaylaştırır. Teşekkürler!
Rory

Xml'nizdeki son Etkinlikten sonra bir XML yorumu vb.Vaka ile uğraşmaya ne dersiniz? yani ReadXml () yöntemini son öğeye okuduğunuzu kontrol eden bir şeyle bitirmelisiniz? Şu anda bu, son Read () işlevinin bunu yaptığını varsayar, ancak her zaman olmayabilir.
Rory

7
@Rory - Örnek eklendi. Geç olsun güç olmasın?
Greg

@Greg İyi bilgi. Ayrıca ReadXml ve WriteXml'in Değişmez Kültürü kullanmasını istemez miydiniz? Kullanıcı başka bir ülkeye taşınıp Bölge ve Dil Ayarlarını değiştirirse sorun yaşayabileceğinizi düşünüyorum. Bu durumda kod doğru şekilde serisini kaldırmayabilir. Serileştirme yaparken her zaman Değişmez Kültürü kullanmanın en iyi yöntem olduğunu okudum
halka açık kablosuz

Yanıtlar:


100

Evet, GetSchema () null değerini döndürmelidir .

IXmlSerializable.GetSchema Yöntemi Bu yöntem ayrılmıştır ve kullanılmamalıdır. IXmlSerializable arabirimini uygularken, bu yöntemden bir boş başvuru (Visual Basic'te Nothing) döndürmelisiniz ve bunun yerine özel bir şema belirtmeniz gerekiyorsa, sınıfa XmlSchemaProviderAttribute öğesini uygulayın.

Hem okuma hem de yazma için, nesne öğesi zaten yazılmıştır, bu nedenle yazma sırasında bir dış öğe eklemenize gerek yoktur. Örneğin, ikisinde nitelikleri okuma / yazma işlemine başlayabilirsiniz.

İçin yazma :

Sağladığınız WriteXml uygulaması, nesnenin XML temsilini yazmalıdır. Çerçeve bir sarmalayıcı öğesi yazar ve XML yazarını başladıktan sonra konumlandırır. Uygulamanız alt öğeler de dahil olmak üzere içeriğini yazabilir. Çerçeve daha sonra sarma elemanını kapatır.

Ve okumak için :

ReadXml yöntemi, WriteXml yöntemi tarafından yazılan bilgileri kullanarak nesnenizi yeniden oluşturmalıdır.

Bu yöntem çağrıldığında, okuyucu, türünüze ilişkin bilgileri saran öğenin başlangıcında konumlandırılır. Yani, serileştirilmiş bir nesnenin başlangıcını gösteren başlangıç ​​etiketinden hemen önce. Bu yöntem döndüğünde, tüm içeriği dahil olmak üzere baştan sona tüm öğeyi okumuş olmalıdır. WriteXml yönteminin aksine, çerçeve sarma öğesini otomatik olarak işlemez. Uygulamanız bunu yapmalıdır. Bu konumlandırma kurallarına uyulmaması, kodun beklenmedik çalışma zamanı özel durumları veya bozuk veriler üretmesine neden olabilir.

Bunun biraz belirsiz olduğunu kabul edeceğim, ancak "bu, Read()sargının son eleman etiketine işinizdir " şeklinde kaybolacaktır.


Etkinlik öğelerini yazma ve okumaya ne dersiniz? Başlangıç ​​elemanını elle yazmak hackish hisseder. Ben birisi her alt öğe yazmak için yazma yönteminde bir XmlSerializer kullandığınızı gördüm.
Greg

@Greg; her iki kullanım da iyidir ... evet, iç içe bir XmlSerializer kullanabilirsiniz, ancak tek seçenek değil.
Marc Gravell

3
Bu hassasiyetler için teşekkürler, MSDN içindeki örnek kod oldukça kullanışsızdır ve bu konuda belirsizdir. Birçok kez takıldım ve Read / WriteXml'in asimetrik davranışını merak ediyordum.
jdehaan

1
@MarcGravell Bunun eski bir konu olduğunu biliyorum. "Çerçeve bir sarmalayıcı öğesi yazar ve XML yazarını başladıktan sonra konumlandırır." Burası mücadele ediyorum. Çerçeveyi, sargıyı otomatik olarak işleme adımını atlamaya zorlamanın bir yolu var mı? Bu adımı
James

@James bilgimin en iyisi değil
Marc Gravell

34

Bu konuda MSDN ile ilgili bir makale yazdım, çünkü MSDN belgeleri şu anda oldukça belirsiz ve web'de bulabileceğiniz örnekler çoğu zaman yanlış uygulanmış durumda.

Tuzaklar Marc Gravell'in daha önce bahsettiklerinin yanı sıra yerelleri ve boş unsurları ele alıyor.

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


Mükemmel makale! Bir dahaki sefere bazı verileri serileştirmek istediğimde kesinlikle referans vereceğim.
Greg

Teşekkürler! olumlu geri bildirim miktarı, yazmak için harcanan süreyi ödüllendirir. Beğendiğiniz için çok minnettarım! Bazı noktaları eleştirmekten çekinmeyin.
jdehaan

Örnekler MSDN'den alıntı yapmaktan çok daha yararlıdır.

Kod projesi için teşekkürler, eğer yapabilirsem oy verirdim. Özniteliklerle ilgili şeyler MSDN'ye kıyasla tamamen kapsamlıydı. Örneğin, my: IXMLSerializable sınıfı, xsd.exe tarafından oluşturulan [Serializable (), XmlType (Namespace = "MonitorService")] ön ekiyle kırıldı.
John

8

Evet, her şey biraz mayın tarlası, değil mi? Marc Gravell'in cevabı hemen hemen bunu kapsıyor, ancak üzerinde çalıştığım bir projede, dış XML öğesini manuel olarak yazmanın oldukça garip olduğunu gördük. Ayrıca, aynı türdeki nesneler için tutarsız XML öğesi adlarıyla sonuçlandı.

Bizim çözümümüz, IXmlSerializablesistemden türeyen, adı verilen bir yöntem ekleyen kendi arayüzümüzü tanımlamaktı WriteOuterXml(). Tahmin edebileceğiniz gibi, bu yöntem sadece dış elemanı yazar, sonra çağırır WriteXml(), sonra elemanın sonunu yazar. Tabii ki, sistem XML serileştiricisi bu yöntemi çağırmaz, bu yüzden sadece kendi serileştirmemizi yaptığımızda yararlı oldu, bu durumda sizin durumunuzda yararlı olabilir veya olmayabilir. Benzer şekilde, ReadContentXml()dış elemanı okumayan, sadece içeriğini okuyan bir yöntem ekledik .


5
C # 3.0 ile bunun yerine bir uzantı yöntemi yazarak bunu yapabilirsiniz, ancak ilginç bir fikir.
Marc Gravell

2

Sınıfınızın zaten bir XmlDocument temsiline sahipseniz veya XML yapılarıyla çalışmanın XmlDocument yolunu tercih ediyorsanız, IXmlSerializable'ı uygulamanın hızlı ve kirli bir yolu, bu xmldoc'u çeşitli işlevlere geçirmektir.

UYARI: XmlDocument (ve / veya XDocument), xmlreader / writer'dan daha yavaş bir büyüklük sırasıdır, bu nedenle performans mutlak bir gereksinimse, bu çözüm sizin için değildir!

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

Arayüz uygulaması diğer cevaplar tarafından kapsanır, ancak kök eleman için 2 sentime atmak istedim.

Geçmişte kök öğeyi meta veri olarak koymayı tercih etmeyi öğrendim. Bunun birkaç faydası vardır:

  • Boş bir nesne varsa, yine de serileştirebilir
  • Kod okunabilirliği açısından mantıklı

Aşağıda, sözlük kök öğesinin bu şekilde tanımlandığı serileştirilebilir bir sözlük örneği verilmiştir:

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

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.