web-dev-qa-db-ja.com

IXmlSerializableを実装する適切な方法は?

プログラマーが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>
146
Greg

はい、GetSchema() nullを返す必要があります

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

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

write の場合:

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

そして read の場合:

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

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

それは少し不明瞭ですが、「ラッパーのRead()要素のタグを作成するのはあなたの仕事です」ということになります。

93
Marc Gravell

MSDNのドキュメントは今のところ非常に不明確であり、Webで見つけることができる例はほとんどの場合正しく実装されていないため、このテーマに関する記事を1つ書いています。

落とし穴は、Marc Gravellが既に言及したものの横にあるロケールと空の要素の処理です。

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

32
jdehaan

はい、すべてが地雷原のようなものですね。 Marc Gravellの答えはそれをほぼカバーしていますが、私が取り組んだプロジェクトでは、外側のXML要素を手動で記述する必要があることが非常に厄介であることがわかりました。また、同じタイプのオブジェクトのXML要素名に矛盾が生じていました。

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

8
EMP

既にクラスの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);
    }
}
2