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サンプルを追加できますか?コードと一緒に読むのが簡単になります。ありがとう!
ロリー

あなたのxmlの最後のイベントの後にXMLコメントなどがある場合の扱いについてはどうですか?つまり、ReadXml()メソッドを最後の要素まで読んだことを確認するもので終了する必要がありますか?現在、これは最後のRead()がそうすることを前提としていますが、常にそうであるとは限りません。
ロリー・

7
@Rory-サンプルが追加されました。遅れることはないですか?
グレッグ

@グレッグ良い情報。また、ReadXmlとWriteXmlでインバリアントカルチャを使用したいと思いませんか?ユーザーが別の国に移動して、地域と言語の設定を変更した場合、問題が発生する可能性があります。その場合、コードは正しくデシリアライズされない可能性があります。シリアル化を行う場合は常にインバリアントカルチャを使用することがベストプラクティスであると読みました
パブリックワイヤレス

回答:


100

はい、GetSchema()はnullを返します

IXmlSerializable.GetSchemaメソッドこのメソッドは予約されているため、使用しないでください。IXmlSerializableインターフェイスを実装する場合、このメソッドからnull参照(Visual BasicではNothing)を返す必要があります。代わりに、カスタムスキーマを指定する必要がある場合は、XmlSchemaProviderAttributeをクラスに適用します。

読み取りと書き込みの両方で、オブジェクト要素はすでに書き込まれているため、書き込みで外部要素を追加する必要はありません。たとえば、2つの属性の読み取り/書き込みを開始できます。

以下のための書き込み

指定するWriteXml実装は、オブジェクトのXML表現を書き出す必要があります。フレームワークはラッパー要素を書き込み、XMLライターを開始後に配置します。実装は、子要素を含むその内容を書き込む場合があります。次に、フレームワークはラッパー要素を閉じます。

そして読むために:

ReadXmlメソッドは、WriteXmlメソッドによって書き込まれた情報を使用してオブジェクトを再構成する必要があります。

このメソッドが呼び出されると、リーダーは、タイプの情報をラップする要素の先頭に配置されます。つまり、シリアル化されたオブジェクトの開始を示す開始タグの直前。このメソッドが戻るとき、その内容のすべてを含む、要素全体を最初から最後まで読み取っていなければなりません。WriteXmlメソッドとは異なり、フレームワークはラッパー要素を自動的に処理しません。あなたの実装はそうする必要があります。これらの配置規則を守らないと、コードで予期しない実行時例外が発生したり、データが破損したりする可能性があります。

少しわかりにくいかもしれませんがRead()、結局のところ「ラッパーのend-elementタグはあなたの仕事です」ということになります。


イベント要素の書き出しと読み取りについてはどうですか?開始要素を手動で作成するのは、やりがいがあります。誰かがwriteメソッドでXmlSerializerを使用して各子要素を書き込むのを見たことがあります。
グレッグ

@Greg; どちらの使用法でもかまいません...はい、必要に応じてネストされたXmlSerializerを使用できますが、これが唯一のオプションではありません。
Marc Gravell

3
これらの精度のおかげで、MSDN内のサンプルコードはこれに関してかなり役に立たず、不明確です。何度も行き詰まり、Read / WriteXmlの非対称動作について疑問に思っていました。
jdehaan 2009年

1
@MarcGravell私はこれが古いスレッドであることを知っています。「フレームワークはラッパー要素を記述し、XMLライターをその開始後に配置します。」これは私が苦労しているところです。フレームワークにラッパーを自動的に処理するこのステップをスキップさせる方法はありますか?:私は、このステップをスキップする必要がある状況持っstackoverflow.com/questions/20885455/...
ジェームズ

@ジェームズは私の知る限りではありません
マークグラベル

34

MSDNのドキュメントは今のところかなり不明確であり、Webで見つけられる例はほとんどの場合正しく実装されていないため、この件について1つの記事をサンプルとともに書きました。

落とし穴は、Marc Gravellがすでに述べたもののほかに、ロケールと空の要素の処理です。

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


逸品!次回、いくつかのデータのシリアル化を検討しているときは、間違いなく参照します。
グレッグ、

ありがとう!肯定的なフィードバックの量は、それを書くのに費やされた時間の量に報います。あなたがそれを好きであることを深く感謝します!いくつかの点を批判することを躊躇しないでください。
jdehaan

例は、MSDNを引用するよりもはるかに便利です。

codeprojectをありがとう、できればそれも投票します。属性に関するものは、MSDNと比較して完全に包括的でした。たとえば、my:IXMLSerializableクラスは、生成されたxsd.exe [Serializable()、XmlType(Namespace = "MonitorService")]をプレフィックスとして使用すると壊れました。
John

8

はい、全部が地雷原のようですね。マークグラベルの回答はそれをほぼカバーしていますが、私が取り組んだプロジェクトにそれを追加したいのですが、外側のXML要素を手動で記述しなければならないのはかなり面倒です。また、同じタイプのオブジェクトのXML要素名に一貫性がなくなりました。

私たちの解決策はIXmlSerializable、と呼ばれるメソッドを追加したシステム1から派生した独自のインターフェースを定義することでしたWriteOuterXml()。ご想像のとおり、このメソッドは外側の要素を書き込んでからを呼び出しWriteXml()、要素の終わりを書き込むだけです。もちろん、システムXMLシリアライザーはこのメソッドを呼び出さないため、独自のシリアル化を行った場合にのみ役立ちました。同様に、ReadContentXml()外側の要素を読み取らず、そのコンテンツのみを読み取るメソッドを追加しました。


5
C#3.0では、代わりに拡張メソッドを書くことでこれを行うことができますが、興味深いアイデアです。
マークグラベル

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.