วิธีที่เหมาะสมในการใช้ IXmlSerializable


153

เมื่อโปรแกรมเมอร์ตัดสินใจที่จะใช้IXmlSerializableสิ่งที่เป็นกฎและแนวทางปฏิบัติที่ดีที่สุดสำหรับการใช้มัน ฉันได้ยินมาว่าGetSchema()ควรกลับมาnullและReadXmlควรย้ายไปองค์ประกอบต่อไปก่อนกลับ มันเป็นเรื่องจริงเหรอ? แล้วWriteXmlมันควรเขียนองค์ประกอบรากสำหรับวัตถุหรือคิดว่ารูทนั้นถูกเขียนไปแล้วหรือไม่? วัตถุลูกควรได้รับการปฏิบัติและเขียนอย่างไร?

นี่คือตัวอย่างของสิ่งที่ฉันมีตอนนี้ ฉันจะอัปเดตเมื่อฉันได้รับคำตอบที่ดี

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 ตัวอย่างที่สอดคล้องกัน

<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
คุณสามารถเพิ่มตัวอย่าง xml ให้กับคำถามนี้ได้ไหม มันจะทำให้ง่ายต่อการอ่านพร้อมกับรหัส ขอบคุณ!
Rory

สิ่งที่เกี่ยวกับการจัดการกับกรณีที่มีความคิดเห็น XML ฯลฯ หลังจากเหตุการณ์สุดท้ายใน xml ของคุณ คือคุณควรเสร็จสิ้นวิธี ReadXml () ด้วยสิ่งที่ตรวจสอบว่าคุณอ่านไปยังองค์ประกอบสิ้นสุดหรือไม่ ขณะนี้สิ่งนี้ถือว่า Read ล่าสุด () ทำเช่นนั้น แต่อาจไม่เสมอไป
Rory

7
@Rory - เพิ่มตัวอย่างแล้ว มาสายดีกว่าไม่มาเลย?
เกร็ก

@Greg ข้อมูลที่ดี คุณไม่ต้องการให้ ReadXml และ WriteXml ใช้วัฒนธรรมแบบคงที่หรือไม่? ฉันคิดว่าคุณอาจพบปัญหาหากผู้ใช้ย้ายไปยังประเทศอื่นและเปลี่ยนการตั้งค่าภูมิภาคและภาษา ในกรณีดังกล่าวรหัสอาจไม่ถูกทำการคำนวณซ้ำอย่างถูกต้อง ฉันได้อ่านว่าเป็นวิธีปฏิบัติที่ดีที่สุดในการใช้ Invariant Culture เสมอเมื่อทำการทำให้เป็นอนุกรม
สาธารณะไร้สาย

คำตอบ:


100

ใช่ GetSchema () ควรคืนค่าเป็นศูนย์

IXmlSerializable.GetSchema วิธีการนี้สงวนไว้และไม่ควรใช้ เมื่อใช้งานอินเตอร์เฟส IXmlSerializable คุณควรส่งคืนการอ้างอิง null (ไม่มีอะไรใน Visual Basic) จากวิธีนี้และหากจำเป็นต้องระบุสกีมาที่กำหนดเองให้ใช้ XmlSchemaProviderAttribute กับคลาสแทน

สำหรับทั้งการอ่านและการเขียนองค์ประกอบของวัตถุได้ถูกเขียนไปแล้วดังนั้นคุณไม่จำเป็นต้องเพิ่มองค์ประกอบด้านนอกในการเขียน ตัวอย่างเช่นคุณสามารถเริ่มอ่าน / เขียนคุณสมบัติในทั้งสอง

สำหรับการเขียน :

การใช้งาน WriteXml ที่คุณระบุควรเขียนการแทนค่า XML ของวัตถุ กรอบงานเขียนองค์ประกอบ wrapper และตำแหน่งตัวเขียน XML หลังจากเริ่มต้น การนำไปใช้ของคุณอาจเขียนเนื้อหารวมถึงองค์ประกอบย่อย กรอบจากนั้นปิดองค์ประกอบ wrapper

และสำหรับการอ่าน :

กระบวนการ ReadXml วิธีต้องสร้างวัตถุของคุณใหม่โดยใช้ข้อมูลที่เขียนโดยวิธีการ WriteXml

เมื่อวิธีนี้เรียกว่าเครื่องอ่านจะอยู่ในตำแหน่งเริ่มต้นขององค์ประกอบที่ล้อมรอบข้อมูลสำหรับประเภทของคุณ นั่นคือก่อนหน้าแท็กเริ่มต้นที่ระบุจุดเริ่มต้นของวัตถุที่เป็นอนุกรม เมื่อวิธีการนี้กลับมามันจะต้องอ่านองค์ประกอบทั้งหมดตั้งแต่ต้นจนจบรวมถึงเนื้อหาทั้งหมด ไม่เหมือนกับเมธอด WriteXml เฟรมเวิร์กไม่ได้จัดการองค์ประกอบ wrapper โดยอัตโนมัติ การดำเนินการของคุณจะต้องทำเช่นนั้น การไม่ปฏิบัติตามกฎการวางตำแหน่งเหล่านี้อาจทำให้รหัสเพื่อสร้างข้อยกเว้นรันไทม์ที่ไม่คาดคิดหรือข้อมูลเสียหาย

ฉันจะยอมรับว่าไม่ชัดเจน แต่มันก็เดือดร้อนลงไปที่ "มันเป็นงานของคุณที่Read()แท็กสิ้นสุดองค์ประกอบของเสื้อคลุม"


แล้วการเขียนและอ่านองค์ประกอบเหตุการณ์ล่ะ รู้สึกว่าแฮ็คต้องเขียนองค์ประกอบเริ่มต้นด้วยตนเอง ฉันคิดว่าฉันเห็นใครบางคนใช้ XmlSerializer ในวิธีการเขียนเพื่อเขียนองค์ประกอบย่อยแต่ละรายการ
เกร็ก

@ Greg; การใช้งานไม่เป็นไร ... ใช่คุณสามารถใช้ XmlSerializer แบบซ้อนหากคุณต้องการ แต่ไม่ใช่ตัวเลือกเดียว
Marc Gravell

3
ขอขอบคุณสำหรับข้อตกลงเหล่านี้โค้ดตัวอย่างใน MSDN นั้นค่อนข้างไม่เหมาะสมและไม่ชัดเจนเกี่ยวกับเรื่องนี้ ฉันติดอยู่หลายครั้งและสงสัยเกี่ยวกับพฤติกรรมที่ไม่สมมาตรของ Read / WriteXml
jdehaan

1
@ MarcGravell ฉันรู้ว่านี่เป็นหัวข้อเก่า "เฟรมเวิร์กเขียนองค์ประกอบ wrapper และวางตำแหน่งตัวเขียน XML หลังจากเริ่มต้น" ที่นี่ฉันกำลังดิ้นรน มีวิธีบังคับกรอบข้ามขั้นตอนของการจัดการเสื้อคลุมนี้โดยอัตโนมัติหรือไม่ ฉันมีสถานการณ์ที่ฉันต้องข้ามขั้นตอนนี้: stackoverflow.com/questions/20885455/ …
James

@ James ไม่ได้ดีที่สุดในความรู้ของฉัน
Marc Gravell

34

ฉันเขียนบทความหนึ่งเรื่องที่มีตัวอย่างเนื่องจากเอกสารของ MSDN นั้นยังไม่ชัดเจนและตัวอย่างที่คุณพบในเว็บนั้นส่วนใหญ่แล้วจะมีการนำไปใช้อย่างไม่ถูกต้อง

ข้อผิดพลาดคือการจัดการสถานที่และองค์ประกอบว่างเปล่านอกเหนือจากสิ่งที่ Marc Gravell พูดถึงแล้ว

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


บทความที่ยอดเยี่ยม! ฉันจะอ้างอิงอย่างแน่นอนในครั้งต่อไปที่ฉันต้องการหาข้อมูลให้เป็นอนุกรม
Greg

ขอบคุณ! จำนวนของผลตอบรับเชิงบวกจะตอบแทนระยะเวลาที่ใช้ในการเขียน ฉันซาบซึ้งมากที่คุณชอบ! อย่าลังเลที่จะขอให้วิจารณ์บางประเด็น
jdehaan

ตัวอย่างมีประโยชน์มากกว่าการอ้างอิง MSDN

ขอบคุณสำหรับ codeproject ฉันจะโหวตมันด้วยถ้าทำได้ สิ่งที่เกี่ยวกับคุณลักษณะที่ครอบคลุมอย่างเต็มที่เมื่อเทียบกับ MSDN ยกตัวอย่างเช่น my: IXMLSerializable class หยุดทำงานเมื่อคำนำหน้าโดย xsd.exe สร้างขึ้น [Serializable (), XmlType (Namespace = "MonitorService")]
John

8

ใช่สิ่งทั้งปวงเป็นเขตที่วางทุ่นระเบิดใช่ไหม? คำตอบของMarc Gravellค่อนข้างครอบคลุม แต่ฉันต้องการเพิ่มในโครงการที่ฉันทำงานเราพบว่ามันค่อนข้างอึดอัดที่จะต้องเขียนองค์ประกอบ XML ภายนอกด้วยตนเอง นอกจากนี้ยังส่งผลให้ชื่อองค์ประกอบ XML ที่ไม่สอดคล้องกันสำหรับวัตถุประเภทเดียวกัน

วิธีการแก้ปัญหาของเราคือการกำหนดของเราเองอินเตอร์เฟซที่มาจากระบบใดระบบหนึ่งซึ่งเพิ่มวิธีการที่เรียกว่าIXmlSerializable WriteOuterXml()อย่างที่คุณสามารถคาดเดาได้วิธีนี้จะเขียนองค์ประกอบนอกแล้วเรียกWriteXml()แล้วเขียนจุดสิ้นสุดขององค์ประกอบ แน่นอน XML ของระบบจะไม่เรียกวิธีนี้ดังนั้นจึงเป็นประโยชน์เฉพาะเมื่อเราทำการซีเรียลไลเซชั่นของเราเองเพื่อที่จะเป็นประโยชน์หรือไม่ในกรณีของคุณ ในทำนองเดียวกันเราได้เพิ่มReadContentXml()วิธีการที่ไม่ได้อ่านองค์ประกอบด้านนอกเพียงเนื้อหาของมัน


5
ด้วย C # 3.0 คุณสามารถทำได้โดยการเขียนวิธีการขยายแทน แต่เป็นแนวคิดที่น่าสนใจ
Marc Gravell

2

หากคุณมี XmlDocument เป็นตัวแทนของชั้นเรียนของคุณหรือชอบวิธี XmlDocument ในการทำงานกับโครงสร้าง XML วิธีที่รวดเร็วและสกปรกในการใช้งาน IXmlSerializable คือการส่งผ่าน xmldoc นี้ไปยังฟังก์ชันต่างๆ

คำเตือน: XmlDocument (และ / หรือ XDocument) เป็นลำดับความสำคัญช้ากว่า xmlreader / นักเขียนดังนั้นหากประสิทธิภาพเป็นข้อกำหนดที่แน่นอนโซลูชันนี้ไม่เหมาะสำหรับคุณ!

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

การใช้อินเทอร์เฟซถูกครอบคลุมโดยคำตอบอื่น ๆ แต่ฉันต้องการที่จะโยนใน 2 เซ็นต์ของฉันสำหรับองค์ประกอบราก

ฉันได้เรียนรู้ในอดีตเพื่อเลือกที่จะวางองค์ประกอบรากเป็นเมทาดาทา สิ่งนี้มีประโยชน์เล็กน้อย:

  • หากมีวัตถุเป็นโมฆะก็ยังสามารถทำให้เป็นอันดับ
  • จากจุดยืนในการอ่านโค้ดมันสมเหตุสมผลดี

ด้านล่างนี้เป็นตัวอย่างของพจนานุกรมแบบอนุกรมที่องค์ประกอบรากพจนานุกรมถูกกำหนดในลักษณะนั้น:

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

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.