Cách thích hợp để thực hiện IXmlSerializable?


153

Một khi lập trình viên quyết định thực hiện IXmlSerializable, các quy tắc và thực tiễn tốt nhất để thực hiện nó là gì? Tôi đã nghe nói rằng GetSchema()nên quay lại nullReadXmlnên chuyển sang phần tử tiếp theo trước khi quay lại. Điều này có đúng không? Và những gì về WriteXml- nó nên viết một phần tử gốc cho đối tượng hay nó được giả định rằng gốc đã được viết? Làm thế nào các đối tượng trẻ em nên được điều trị và viết?

Đây là một mẫu của những gì tôi có bây giờ. Tôi sẽ cập nhật nó khi tôi nhận được phản hồi tốt.

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 mẫu tương ứng

<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
Bạn có thể thêm một mẫu xml cho câu hỏi này? Nó sẽ làm cho nó đơn giản hơn để đọc cùng với mã. Cảm ơn!
Rory

Còn về việc xử lý trường hợp có bình luận XML, v.v ... sau Sự kiện cuối cùng trong xml của bạn. tức là bạn có nên hoàn thành phương thức ReadXml () với thứ gì đó kiểm tra xem bạn đã đọc đến phần tử cuối không? Hiện tại, điều này giả sử Read () cuối cùng thực hiện điều đó nhưng có thể không phải lúc nào cũng vậy.
Rory

7
@Rory - Mẫu được thêm vào. Muộn còn hơn không?
Greg

@Greg Thông tin tốt. Bạn cũng không muốn có ReadXml và WriteXml sử dụng Văn hóa bất biến chứ? Tôi nghĩ bạn có thể gặp sự cố nếu người dùng chuyển đến một quốc gia khác và thay đổi Cài đặt ngôn ngữ và khu vực của họ. Trong trường hợp đó, mã có thể không khử lưu chính xác. Tôi đã đọc rằng đó là cách tốt nhất để luôn sử dụng Văn hóa bất biến khi thực hiện nối tiếp
mạng không dây công cộng

Câu trả lời:


100

Có, GetSchema () sẽ trả về null .

Phương pháp IXmlSerializable.GetSchema Phương pháp này được bảo lưu và không nên được sử dụng. Khi triển khai giao diện IXmlSerializable, bạn nên trả về tham chiếu null (Không có gì trong Visual Basic) từ phương thức này và thay vào đó, nếu chỉ định một lược đồ tùy chỉnh là bắt buộc, hãy áp dụng XmlSchemaProviderAttribution cho lớp.

Đối với cả đọc và viết, phần tử đối tượng đã được viết, vì vậy bạn không cần thêm phần tử bên ngoài vào phần ghi. Ví dụ, bạn chỉ có thể bắt đầu đọc / ghi thuộc tính trong hai.

Để viết :

Việc triển khai WriteXml mà bạn cung cấp sẽ viết ra biểu diễn XML của đối tượng. Khung công tác viết một phần tử trình bao bọc và định vị trình soạn thảo XML sau khi bắt đầu. Việc thực hiện của bạn có thể viết nội dung của nó, bao gồm các yếu tố con. Khung sau đó đóng phần tử bao bọc.

Và để đọc :

Phương thức ReadXml phải khôi phục đối tượng của bạn bằng cách sử dụng thông tin được viết bởi phương thức WriteXml.

Khi phương thức này được gọi, trình đọc được định vị ở đầu phần tử bao bọc thông tin cho loại của bạn. Đó là, ngay trước thẻ bắt đầu chỉ ra sự bắt đầu của một đối tượng được tuần tự hóa. Khi phương thức này trả về, nó phải đọc toàn bộ phần tử từ đầu đến cuối, bao gồm tất cả các nội dung của nó. Không giống như phương thức WriteXml, khung công tác không tự động xử lý phần tử trình bao bọc. Việc thực hiện của bạn phải làm như vậy. Không tuân thủ các quy tắc định vị này có thể khiến mã tạo ra ngoại lệ thời gian chạy không mong muốn hoặc dữ liệu bị hỏng.

Tôi sẽ đồng ý rằng điều đó không rõ ràng một chút, nhưng nó hiểu rõ "đó là công việc của bạn đối Read()với thẻ phần tử cuối của trình bao bọc".


Còn việc viết ra và đọc các yếu tố Sự kiện thì sao? Nó cảm thấy hackish để tự viết phần tử bắt đầu. Tôi nghĩ rằng tôi đã thấy ai đó sử dụng XmlSerializer trong phương thức ghi để viết từng phần tử con.
Greg

@Greg; sử dụng đều ổn ... vâng, bạn có thể sử dụng XmlSerializer lồng nhau nếu bạn cần, nhưng đó không phải là lựa chọn duy nhất.
Marc Gravell

3
Cảm ơn những quy định này, mã mẫu trong MSDN khá khó tin và không rõ ràng về vấn đề này. Tôi đã bị mắc kẹt nhiều lần và đã tự hỏi về hành vi bất đối xứng của Read / WriteXml.
jdehaan

1
@MarcGravell Tôi biết đây là một chủ đề cũ. "Khung công tác viết một phần tử trình bao bọc và định vị trình soạn thảo XML sau khi bắt đầu." Đây là nơi tôi đang vật lộn. Có cách nào để buộc khung bỏ qua bước này để tự động xử lý trình bao bọc không? Tôi có một tình huống cần bỏ qua bước này: stackoverflow.com/questions/20885455/iêu
James

@James không theo sự hiểu biết tốt nhất của tôi
Marc Gravell

34

Tôi đã viết một bài viết về chủ đề này với các mẫu vì tài liệu MSDN hiện chưa rõ ràng và các ví dụ bạn có thể tìm thấy trên web hầu hết thời gian được thực hiện không chính xác.

Cạm bẫy đang xử lý các địa phương và các yếu tố trống rỗng bên cạnh những gì Marc Gravell đã đề cập.

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


Bài viết tuyệt vời! Tôi chắc chắn sẽ tham khảo nó vào lần tới khi tôi đang tìm cách tuần tự hóa một số dữ liệu.
Greg

Cảm ơn! lượng phản hồi tích cực thưởng cho lượng thời gian đầu tư vào việc viết nó. Tôi đánh giá cao rằng bạn thích nó! Đừng ngần ngại yêu cầu chỉ trích một số điểm.
jdehaan

Các ví dụ hữu ích hơn nhiều so với trích dẫn MSDN.

Cảm ơn vì mật mã, tôi cũng sẽ bỏ phiếu nếu tôi có thể. Các công cụ về thuộc tính hoàn toàn toàn diện so với MSDN. Chẳng hạn, lớp: IXMLSerializable của tôi đã bị hỏng khi tiền tố bởi xsd.exe được tạo [serializable (), XmlType (Namespace = "MonitorService")].
Giăng

8

Vâng, toàn bộ là một chút của một bãi mìn, phải không? Câu trả lời của Marc Gravell bao gồm khá nhiều, nhưng tôi muốn nói thêm rằng trong một dự án tôi đã làm việc, chúng tôi thấy khá lúng túng khi phải tự viết phần tử XML bên ngoài. Nó cũng dẫn đến các tên thành phần XML không nhất quán cho các đối tượng cùng loại.

Giải pháp của chúng tôi là xác định IXmlSerializablegiao diện riêng của chúng tôi , xuất phát từ hệ thống, đã thêm một phương thức gọi là WriteOuterXml(). Như bạn có thể đoán, phương thức này chỉ đơn giản là viết phần tử bên ngoài, sau đó gọi WriteXml(), sau đó viết phần cuối của phần tử. Tất nhiên, trình tuần tự hóa XML hệ thống sẽ không gọi phương thức này, vì vậy nó chỉ hữu ích khi chúng tôi tự thực hiện tuần tự hóa, do đó có thể có hoặc không hữu ích trong trường hợp của bạn. Tương tự, chúng tôi đã thêm một ReadContentXml()phương thức, không đọc phần tử bên ngoài, chỉ nội dung của nó.


5
Với C # 3.0, bạn có thể có thể làm điều này bằng cách viết một phương thức mở rộng thay thế, nhưng một ý tưởng thú vị.
Marc Gravell

2

Nếu bạn đã có một đại diện XmlDocument của lớp của bạn hoặc thích cách làm việc với các cấu trúc XML của XmlDocument, cách triển khai IXmlSerializable nhanh chóng và bẩn thỉu là chỉ cần chuyển xmldoc này cho các hàm khác nhau.

CẢNH BÁO: XmlDocument (và / hoặc XDocument) là một thứ tự cường độ chậm hơn so với xmlreader / nhà văn, vì vậy nếu hiệu suất là một yêu cầu tuyệt đối, giải pháp này không dành cho bạn!

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

Việc thực hiện giao diện được bao phủ bởi các câu trả lời khác, nhưng tôi muốn ném 2 xu của mình cho phần tử gốc.

Trước đây tôi đã học cách thích đặt phần tử gốc làm siêu dữ liệu. Điều này có một vài lợi ích:

  • Nếu có một đối tượng null, nó vẫn có thể tuần tự hóa
  • Từ quan điểm dễ đọc mã, nó có ý nghĩa

Dưới đây là một ví dụ về một từ điển tuần tự hóa trong đó phần tử gốc từ điển được xác định theo cách đó:

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

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.