1. ホーム
  2. c#

[解決済み] XMLシリアライズと継承型

2023-05-06 06:45:01

質問

私の 前の質問 私は私のオブジェクトモデルをXMLにシリアライズすることに取り組んできました。しかし、私は今問題に遭遇しました(驚きです!)。

私が抱えている問題は、抽象的な基本クラスタイプのコレクションを持っていて、それが具体的な派生タイプによって構成されていることです。

私は、関係するすべてのクラスに XML 属性を追加するだけでよく、すべてがうまくいくだろうと思いました。悲しいことに、そうではありません!

そこで、Google でいくつか調べてみたところ、次のことがわかりました。 なぜ がうまくいかない理由がわかりました。その中で XmlSerializer は実際にはXMLとの間でオブジェクトを直列化するために巧妙なリフレクションを行っており、抽象的な型に基づいているため、それが一体何と話しているのかを理解することができません。 . 結構です。

確かに私は このページ をCodeProjectで見つけました。それは多くのことを助けるように見えますが(まだ完全に読んで/消費していません)、私はこの問題をStackOverflowのテーブルにも持って行きたいと思いました。

1 つ付け加えるとすれば、私は はしないことです。 を実行したいと思います。 XmlInclude のルートには入りたくありません。これには単純に多くのカップリングがあり、システムのこのエリアは重い開発中であるため、メンテナンスの頭痛の種になるでしょう!

どのように解決するのですか?

問題解決

さて、ようやくたどり着いたわけですが、(確かに ロット の助けを借りて)。 ここで !).

では、まとめます。

目標

  • の下に行きたくなかった。 XmlInclude のルートは、メンテナンスの頭痛の種になるため、使いたくありませんでした。
  • 解決策が見つかったら、他のアプリケーションに素早く実装できるようにしたいと思いました。
  • 抽象的な型のコレクションは、個々の抽象的なプロパティと同様に使用することができます。
  • 具象クラスで "特別な"ことをしなければならないことに煩わされたくなかったのです。

特定された問題点/注意点。

  • XmlSerializer はかなりクールなリフレクションを行いますが、それは は非常に 抽象的な型に関しては制限されています (つまり、サブクラスではなく、抽象的な型そのもののインスタンスでのみ動作します)。
  • Xml 属性デコレータは、XmlSerializer が検出したプロパティをどのように扱うかを定義します。物理的な型を指定することもできますが、その場合は タイトなカップリング クラスとシリアライザーの間にが発生します (良くないことです)。
  • を実装したクラスを作成することで、独自のXmlSerializerを実装することができます。 IXmlSerializable .

解決策

ジェネリッククラスを作成しました。このクラスでは、ジェネリック型を作業対象となる抽象型として指定します。これにより、キャスティングをハードコーディングできるため、抽象型と具象型の間で "translate"できるようになりました (つまり、XmlSerializer ができる以上の情報を取得できるようになりました)。

次に、私は IXmlSerializable インターフェイスを実装します。これは非常に簡単ですが、シリアライズするときに、具象クラスの型を XML に書き込むことを確認する必要があり、シリアライズを解除するときにそれをキャストして戻すことができます。また、このクラスは 完全修飾 でなければならないことにも注意してください。もちろん、ここで起こる必要のある小さな型チェックや何かがあります。

XmlSerializerはキャストできないので、それを行うコードを提供する必要があり、暗黙の演算子はオーバーロードされます (こんなことができるなんて知らなかった!)。

AbstractXmlSerializerのコードはこれです。

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

namespace Utility.Xml
{
    public class AbstractXmlSerializer<AbstractType> : IXmlSerializable
    {
        // Override the Implicit Conversions Since the XmlSerializer
        // Casts to/from the required types implicitly.
        public static implicit operator AbstractType(AbstractXmlSerializer<AbstractType> o)
        {
            return o.Data;
        }

        public static implicit operator AbstractXmlSerializer<AbstractType>(AbstractType o)
        {
            return o == null ? null : new AbstractXmlSerializer<AbstractType>(o);
        }

        private AbstractType _data;
        /// <summary>
        /// [Concrete] Data to be stored/is stored as XML.
        /// </summary>
        public AbstractType Data
        {
            get { return _data; }
            set { _data = value; }
        }

        /// <summary>
        /// **DO NOT USE** This is only added to enable XML Serialization.
        /// </summary>
        /// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
        public AbstractXmlSerializer()
        {
            // Default Ctor (Required for Xml Serialization - DO NOT USE)
        }

        /// <summary>
        /// Initialises the Serializer to work with the given data.
        /// </summary>
        /// <param name="data">Concrete Object of the AbstractType Specified.</param>
        public AbstractXmlSerializer(AbstractType data)
        {
            _data = data;
        }

        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null; // this is fine as schema is unknown.
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            // Cast the Data back from the Abstract Type.
            string typeAttrib = reader.GetAttribute("type");

            // Ensure the Type was Specified
            if (typeAttrib == null)
                throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because no 'type' attribute was specified in the XML.");

            Type type = Type.GetType(typeAttrib);

            // Check the Type is Found.
            if (type == null)
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the type specified in the XML was not found.");

            // Check the Type is a Subclass of the AbstractType.
            if (!type.IsSubclassOf(typeof(AbstractType)))
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the Type specified in the XML differs ('" + type.Name + "').");

            // Read the Data, Deserializing based on the (now known) concrete type.
            reader.ReadStartElement();
            this.Data = (AbstractType)new
                XmlSerializer(type).Deserialize(reader);
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            // Write the Type Name to the XML Element as an Attrib and Serialize
            Type type = _data.GetType();

            // BugFix: Assembly must be FQN since Types can/are external to current.
            writer.WriteAttributeString("type", type.AssemblyQualifiedName);
            new XmlSerializer(type).Serialize(writer, _data);
        }

        #endregion
    }
}

では、そこからどうやってXmlSerializerにデフォルトではなく、私たちのシリアライザーを使うように指示するのでしょうか?例えば、Xml属性のtypeプロパティ内で私たちの型を渡す必要があります。

[XmlRoot("ClassWithAbstractCollection")]
public class ClassWithAbstractCollection
{
    private List<AbstractType> _list;
    [XmlArray("ListItems")]
    [XmlArrayItem("ListItem", Type = typeof(AbstractXmlSerializer<AbstractType>))]
    public List<AbstractType> List
    {
        get { return _list; }
        set { _list = value; }
    }

    private AbstractType _prop;
    [XmlElement("MyProperty", Type=typeof(AbstractXmlSerializer<AbstractType>))]
    public AbstractType MyProperty
    {
        get { return _prop; }
        set { _prop = value; }
    }

    public ClassWithAbstractCollection()
    {
        _list = new List<AbstractType>();
    }
}

ここでは、コレクションと単一のプロパティが公開されていることがわかります。 タイプ という名前のパラメータをXml宣言に追加するだけです。簡単ですね。:D

注:このコードを使用する場合、私は本当に賞賛を感謝します。また、より多くの人々をコミュニティに誘導するのに役立つでしょう :)

さて、しかし、すべての回答には長所と短所があったので、ここでの回答をどうするか迷っています。私は有用であると感じたものをアップモデし(そうでないものに悪気はありません)、私が評判を得たらこれを閉じます :)

面白い問題で、解決するのが楽しいです! :)