86

以前の質問に続いて、オブジェクト モデルを XML にシリアル化する作業を続けています。しかし、私は今問題に遭遇しました (quelle 驚き!)。

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

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

だから私はグーグルで掘り下げましたが、なぜそれが機能していないのか理解しました. 実際、XmlSerializerXML との間でオブジェクトをシリアル化するために巧妙なリフレクションを行っていますが、抽象型に基づいているため、一体何と話しているのかを理解できません。罰金。

私はCodeProject でこのページに出くわしましたが、これは大いに役立つように見えます (まだ完全に読んだり消費したりしていません)。これを可能な限り迅速/軽量な方法で起動して実行するためのハック/トリック。

また、追加しなければならないことの 1 つは、ルートをたどりたくないということです。XmlIncludeそれとの結合が多すぎるだけでなく、システムのこの領域は大規模な開発が行われているため、メンテナンスの頭痛の種になるでしょう!

4

7 に答える 7

56

問題が解決しました!

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

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

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

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

于 2009-06-12T07:42:23.883 に答える
9

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

extraTypes パラメータを持つ XmlSerialiser コンストラクタ

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

于 2008-08-21T14:35:04.173 に答える
3

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

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

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

于 2008-08-21T14:41:03.693 に答える
2

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

type.AssemblyQualifiedName

次のように見えます

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

これには、アセンブリ属性とバージョンが含まれています...

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

于 2008-12-25T09:49:27.243 に答える
2

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

もう少し調査を行うだけで、コードをソートする必要があるだけで勝者になっているようです。

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

  • XmlSeralizerは、基本的に、シリアル化するクラスに気の利いたリフレクションを行うクラスです。Typeに基づいてシリアル化されるプロパティを決定します。
  • 問題が発生する理由は、タイプの不一致が発生しているためです。BaseType を期待していますが、実際にはDerivedTypeを受け取ります。ポリモーフィックに扱うと思うかもしれませんが、余分な負荷がかかるためそうではありません。リフレクションと型チェック。これは設計されていません。

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

このスペースをご覧ください!^_^

于 2008-08-22T09:00:51.940 に答える
1

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

于 2008-08-21T14:34:43.910 に答える