XMLシリアル化と継承されたタイプ


85

前の質問に続いて、オブジェクトモデルをXMLにシリアル化する作業を行っています。しかし、私は今問題に遭遇しました(クエルサプライズ!)。

私が抱えている問題は、抽象基本クラス型のコレクションがあり、具体的な派生型が入力されていることです。

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

だから私はグーグルでいくつかの掘り下げをしました、そして今私はそれがうまくいかない理由を理解しました。その中で実際にあるが、シリアライズするためにいくつかの巧妙な反射をやってXMLから/に反対し、その抽象型に基づいているので、それがに話して何地獄を把握することはできません。結構です。XmlSerializer

私はCodeProjectでこのページに出くわしましたが、それは大いに役立つかもしれません(まだ完全に読んだり消費したりする)が、この問題をStackOverflowテーブルにも持ってきて、きちんとしたものがあるかどうかを確認したいと思いましたこれを可能な限り迅速/最軽量の方法で稼働させるためのハック/トリック。

私が付け加えなければならないことの一つは、私がルートを下りたくないということXmlIncludeです。単にそれとの結合が多すぎて、システムのこの領域は開発が進んでいるので、それは本当にメンテナンスの頭痛の種になるでしょう!


1
シリアル化しようとしているクラスから抽出されたいくつかの関連するコードスニペットを確認すると便利です。
レックスM

メイト:他の人がこれを便利だと思うので再開しましたが、同意しない場合は気軽に閉じてください
JamesSugrue 2008年

このスレッドには長い間何もなかったので、これで少し混乱しましたか?
ロブ・クーパー

回答:


54

問題が解決しました!

OK、それで私はついにそこに着きました(確かにここからたくさんの助けを借り!)。

要約すると:

目標:

  • メンテナンスの頭痛の種のため、XmlIncludeルートを下りたくありませんでした。
  • 解決策が見つかったら、他のアプリケーションにすばやく実装できるようにしたかったのです。
  • 個々の抽象プロパティだけでなく、抽象型のコレクションも使用できます。
  • 具体的なクラスで「特別な」ことをしなければならないことを本当に気にしたくありませんでした。

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

  • XmlSerializerはかなりクールなリフレクションを実行しますが、抽象型に関しては非常に制限されています(つまり、サブクラスではなく、抽象型自体のインスタンスでのみ機能します)。
  • Xml属性デコレータは、XmlSerializerが検出したプロパティをどのように処理するかを定義します。物理タイプも指定できますが、これにより、クラスとシリアライザーの間に緊密な結合が作成されます(良くありません)。
  • IXmlSerializableを実装するクラスを作成することにより、独自のXmlSerializerを実装できます。

ソリューション

ジェネリッククラスを作成しました。このクラスでは、使用する抽象型としてジェネリック型を指定します。これにより、キャストをハードコーディングできるため(つまり、XmlSerializerよりも多くの情報を取得できるため)、クラスは抽象型と具象型の間で「変換」することができます。

次に、IXmlSerializableインターフェイスを実装しました。これは非常に簡単ですが、シリアル化するときは、具象クラスの型をXMLに書き込む必要があります。これにより、逆シリアル化時にキャストバックできます。2つのクラスが含まれるアセンブリは異なる可能性があるため、完全に修飾されている必要があること注意することも重要です。もちろん、ここで行う必要のある小さな型チェックなどがあります。

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

注:このコードを使用する場合は、大声でお願いします。また、より多くの人々をコミュニティに呼び込むのにも役立ちます:)

さて、しかし、彼ら全員が彼らの賛否両論を持っていたので、ここで答えをどうするかについてはわかりません。私は有用だと思うものをアップモッドし(そうでないものには不快感はありません)、担当者ができたらこれを閉じます:)

興味深い問題と解決するのが楽しい!:)


私はしばらく前にこの問題に遭遇しました。個人的には、XmlSerializerを放棄し、IXmlSerializableインターフェイスを直接使用することになりました。これは、すべてのクラスでXmlSerializerを実装する必要があるためです。それ以外の点では、ソリューションは非常に似ています。でも良い記事:)
Thorarin

リストを配列に変換するXML_プロパティを使用します:)
Arcturus

2
クラスを動的にインスタンス化するには、パラメーターのないコンストラクターが必要なためです。
Silas Hansen 2010

1
こんにちは!私はかなり前からこのような解決策を探していました。素晴らしいと思います!使い方がわからないのですが、例を挙げていただけませんか?オブジェクトを含むクラスまたはリストをシリアル化しますか?
ダニエル

1
素敵なコード。パラメータのないコンストラクタを宣言しprivateたりprotected、他のクラスで使用できないように強制したりできることに注意してください。
tcovo 2011年

9

注目すべきことの1つは、XmlSerialiserコンストラクターで、シリアライザーが解決するのが難しい可能性のある型の配列を渡すことができるという事実です。コレクションまたはデータ構造の複雑なセットをシリアル化する必要があり、それらのタイプが異なるアセンブリなどに存在する場合は、これを何度も使用する必要がありました。

extraTypesパラメーターを指定したXmlSerialiserコンストラクター

編集:このアプローチには、実行時に可能な具体的なタイプのリストを検出してコンパイルし、それらを詰め込む方法を考え出すことができるXmlInclude属性などよりも利点があることを付け加えておきます。


これは私がやろうとしていることですが、私が考えていたように簡単ではありません:stackoverflow.com/questions/3897818/…–
Luca

これは非常に古い投稿ですが、これを実装することを検討している人は、extraTypes paramを使用したXmlSerializerのコンストラクターが、生成したアセンブリをその場でキャッシュしないことに注意してください。これには、メモリリークのデバッグに数週間かかります。したがって、受け入れられた回答のコードで追加の型を使用する場合は、シリアライザーをキャッシュします。この動作は次の場所に記載されています:support.microsoft.com/en-us/kb/886385
Julien Lebot 2016

3

真剣に、POCOの拡張可能なフレームワークがXMLに確実にシリアル化されることは決してありません。私は誰かがやって来てあなたのクラスを拡張し、それを台無しにすることを保証できるので、これを言います。

オブジェクトグラフをシリアル化するためにXAMLを使用することを検討する必要があります。これを行うように設計されていますが、XMLシリアル化はそうではありません。

問題なくXAMLのシリアライザとデシリアライザハンドルジェネリック、ならびに基底クラスとインタフェースのコレクション(限り、コレクション自体が実装IList又はIDictionary)。読み取り専用コレクションのプロパティをでマークするなど、いくつかの注意点がありますが、DesignerSerializationAttributeこれらのコーナーケースを処理するためにコードを作り直すことはそれほど難しくありません。


リンクが切れているようです
bkribbs 2015

しかたがない。私はそのビットを核兵器にします。この主題に関する他の多くのリソース。

2

これについての簡単な更新、私は忘れていません!

さらに調査を行うだけで、勝者に向かっているように見えます。コードを並べ替える必要があります。

これまでのところ、私は次のものを持っています:

  • XmlSeralizerは、基本的にはシリアル化されたクラスにいくつかの気の利いた反射を行うクラスです。Typeに基づいてシリアル化されるプロパティを決定します。
  • 問題が発生する理由は、型の不一致が発生しているためです。BaseTypeを予期していますが、実際にはDerivedTypeを受け取ります。多態的に処理すると思われるかもしれませんが、リフレクションと型チェック。これは設計されていません。

この動作は、シリアライザーの仲介役として機能するプロキシクラスを作成することで、オーバーライド(コード保留)できるようです。これにより、基本的に派生クラスのタイプが判別され、通常どおりシリアル化されます。次に、このプロキシクラスは、そのXMLをメインシリアライザーにバックアップします。

この空間を見て!^ _ ^


2

これは確かに問題の解決策ですが、「ポータブル」XML形式を使用する意図をいくらか損なう別の問題があります。プログラムの次のバージョンでクラスを変更することを決定し、シリアル化の両方の形式(新しいものと古いもの)をサポートする必要がある場合、悪いことが起こります(クライアントがまだ古いファイル/データベースを使用しているか、に接続しているため)古いバージョンの製品を使用しているサーバー)。しかし、あなたが使用したので、あなたはもうこのシリアライザーを使用することはできません

type.AssemblyQualifiedName

のように見えます

TopNamespace.SubNameSpace.ContainingClass+NestedClass, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089

つまり、アセンブリの属性とバージョンが含まれています。

アセンブリのバージョンを変更しようとした場合、または署名することにした場合、この逆シリアル化は機能しません...


1

私はこれと同じようなことをしました。私が通常行うことは、すべてのXMLシリアル化属性が具象クラスにあることを確認し、そのクラスのプロパティが基本クラス(必要な場合)に呼び出されて、シリアライザーが呼び出すときに逆シリアル化される情報を取得することです。それらのプロパティ。これはもう少しコーディング作業ですが、シリアライザーに正しいことを強制しようとするよりもはるかにうまく機能します。


1

さらに良いことに、表記法を使用します。

[XmlRoot]
public class MyClass {
    public abstract class MyAbstract {} 
    public class MyInherited : MyAbstract {} 
    [XmlArray(), XmlArrayItem(typeof(MyInherited))] 
    public MyAbstract[] Items {get; set; } 
}

2
あなたがあなたのクラスを知っているなら、これは素晴らしいです、それは最もエレガントな解決策です。外部ソースから新しい継承クラスをロードする場合、残念ながらそれを使用することはできません。
ウラジミール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.