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를 사용하도록 하시겠습니까? 사용자가 다른 국가로 이동하여 지역 및 언어 설정을 변경하면 문제가 발생할 수 있습니다. 이 경우 코드가 올바르게 역 직렬화되지 않을 수 있습니다. 직렬화를 수행 할 때 항상 Invariant Culture를 사용하는 것이 가장 좋습니다.
public wireless

답변:


100

예, GetSchema () 는 null을 반환해야합니다 .

IXmlSerializable.GetSchema 메서드이 메서드는 예약되어 있으므로 사용해서는 안됩니다. IXmlSerializable 인터페이스를 구현할 때이 메소드에서 널 참조 (Visual Basic의 경우 Nothing)를 리턴해야하며 대신 사용자 정의 스키마를 지정해야하는 경우 XmlSchemaProviderAttribute를 클래스에 적용하십시오.

읽기 및 쓰기 모두에 대해 오브젝트 요소가 이미 작성되었으므로 외부 요소를 쓰기에 추가 할 필요가 없습니다. 예를 들어 두 속성에서 읽기 / 쓰기 속성을 시작할 수 있습니다.

을 위해 쓰기 :

제공하는 WriteXml 구현은 객체의 XML 표현을 작성해야합니다. 프레임 워크는 랩퍼 요소를 작성하고 시작 후 XML 작성기를 배치합니다. 구현시 자식 요소를 포함하여 해당 내용을 작성할 수 있습니다. 그런 다음 프레임 워크가 랩퍼 요소를 닫습니다.

그리고 읽기를 위해 :

ReadXml 메서드는 WriteXml 메서드로 작성된 정보를 사용하여 개체를 재구성해야합니다.

이 메소드가 호출되면 리더는 사용자 유형의 정보를 랩핑하는 요소의 시작 부분에 위치합니다. 즉, 직렬화 된 객체의 시작을 나타내는 시작 태그 바로 앞에 있습니다. 이 메소드가 리턴되면 모든 내용을 포함하여 처음부터 끝까지 전체 요소를 읽었어야합니다. WriteXml 메서드와 달리 프레임 워크는 래퍼 요소를 자동으로 처리하지 않습니다. 구현해야합니다. 이러한 위치 지정 규칙을 준수하지 않으면 코드에서 예기치 않은 런타임 예외가 발생하거나 데이터가 손상 될 수 있습니다.

나는 그것이 다소 불분명하다는 것에 동의 할 것이지만, 그것은 " Read()래퍼의 끝 요소 태그에 대한 당신의 임무"로 귀결됩니다 .


이벤트 요소를 쓰고 읽는 것은 어떻습니까? 시작 요소를 수동으로 작성하는 것이 어색합니다. 누군가 누군가 write 메소드에서 XmlSerializer를 사용하여 각 자식 요소를 쓰는 것을 보았습니다.
Greg

@ 그레그; 어느 쪽 사용법이든 괜찮습니다 ... 예, 필요한 경우 중첩 된 XmlSerializer를 사용할 수 있지만 이것이 유일한 옵션은 아닙니다.
Marc Gravell

3
이러한 정밀도 덕분에 MSDN 내부의 샘플 코드는 이것에 대해 매우 유용하지 않고 명확하지 않습니다. 여러 번 붙어서 Read / WriteXml의 비대칭 동작이 궁금합니다.
jdehaan

1
@MarcGravell 나는 이것이 오래된 스레드라는 것을 알고있다. "프레임 워크는 래퍼 요소를 작성하고 시작 후 XML 작성기를 배치합니다." 내가 고군분투하는 곳입니다. 래퍼가 자동으로 래퍼를 처리하는이 단계를 건너 뛰도록하는 방법이 있습니까? 이 단계를 건너 뛰어야 할 상황이 있습니다 : stackoverflow.com/questions/20885455/…
James

@James 내 지식의 최고가 아닙니다
Marc Gravell

34

MSDN 문서가 이제 명확하지 않고 웹에서 찾을 수있는 예제가 대부분 잘못 구현되어 샘플로 주제에 대한 기사를 썼습니다.

함정은 Marc Gravell이 이미 언급 한 것 외에도 로케일과 빈 요소를 처리합니다.

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


훌륭한 기사! 다음에 데이터를 직렬화하려고 할 때 분명히 참조 할 것입니다.
Greg Greg

감사! 긍정적 인 피드백의 양은 그것을 작성하는데 투자 한 시간의 양을 보상합니다. 나는 당신이 그것을 좋아한다는 것을 깊이 감사합니다! 주저하지 말고 비판을 요청하십시오.
jdehaan

예제는 MSDN을 인용하는 것보다 훨씬 유용합니다.

코드 프로젝트에 감사드립니다. 가능하다면 투표도하겠습니다. 속성에 관한 내용은 MSDN에 비해 완전히 포괄적이었습니다. 예를 들어, my : IXMLSerializable 클래스는 xsd.exe가 접두사 [Serializable (), XmlType (Namespace = "MonitorService")]를 생성 할 때 중단되었습니다.
John

8

예, 모든 것이 약간의 지뢰밭입니다. Marc Gravell 의 답변은 거의 대부분을 다루고 있지만, 내가 작업 한 프로젝트에서 외부 XML 요소를 수동으로 작성 해야하는 것이 상당히 어색하다는 것을 추가하고 싶습니다. 또한 동일한 유형의 오브젝트에 대해 XML 요소 이름이 일치하지 않습니다.

우리의 솔루션은 IXmlSerializable시스템에서 파생 된 자체 인터페이스 를 정의하고 라는 메소드를 추가하는 것이 었습니다 WriteOuterXml(). 짐작할 수 있듯이이 메소드는 단순히 외부 요소 WriteXml()를 작성한 다음을 호출 한 다음 요소의 끝을 작성합니다. 물론 시스템 XML serializer는이 메서드를 호출하지 않으므로 자체 직렬화를 수행 할 때만 유용하므로 귀하의 경우에는 도움이 될 수도 있고 그렇지 않을 수도 있습니다. 마찬가지로 ReadContentXml()외부 요소를 읽지 않고 내용 만 읽는 메소드를 추가했습니다 .


5
C # 3.0을 사용하면 확장 메소드를 작성하는 대신 흥미로운 아이디어를 작성하여이를 수행 할 수 있습니다.
Marc Gravell

2

클래스의 XmlDocument 표현이 이미 있거나 XML 구조를 사용하는 XmlDocument 방식을 선호하는 경우 IXmlSerializable을 구현하는 빠르고 더러운 방법은이 xmldoc을 다양한 함수에 전달하는 것입니다.

경고 : XmlDocument (및 / 또는 XDocument)는 xmlreader / writer보다 훨씬 느리므로 성능이 절대적인 요구 사항이라면이 솔루션은 적합하지 않습니다!

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 센트를 던지기를 원했습니다.

과거에는 루트 요소를 메타 데이터로 사용하는 것을 선호하는 법을 배웠습니다. 몇 가지 장점이 있습니다.

  • null 개체가 있으면 여전히 직렬화 할 수 있습니다
  • 코드 가독성 관점에서 말이됩니다.

아래는 사전 루트 요소가 그런 식으로 정의 된 직렬화 가능한 사전의 예입니다.

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.