.NET XMLシリアライゼーションの問題?[閉まっている]


121

共有したいと思っていたC#XMLシリアル化を実行するときに、いくつかの落とし穴に遭遇しました。

  • 読み取り専用のアイテム(KeyValuePairsなど)をシリアル化することはできません
  • 一般的な辞書をシリアル化することはできません。代わりに、このラッパークラスを試してください(http://weblogs.asp.net/pwelter34/archive/2006/05/03/444961.aspxから):

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

[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{      
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new 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();

            this.Add(key, value);

            reader.ReadEndElement();
            reader.MoveToContent();
        }
        reader.ReadEndElement();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

        foreach (TKey key in this.Keys)
        {
            writer.WriteStartElement("item");

            writer.WriteStartElement("key");
            keySerializer.Serialize(writer, key);
            writer.WriteEndElement();

            writer.WriteStartElement("value");
            TValue value = this[key];
            valueSerializer.Serialize(writer, value);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
    }
}

他のXMLシリアライゼーションはそこにありますか?


より多くの落とし穴のためのlookin笑、あなたは私を助けることができるかもしれない:stackoverflow.com/questions/2663836/...
シミーWeitzhandler

1
また、あなたがserialzable辞書のチャールズFedukeの実装に見てみたいと思うでしょう、彼はデフォルトのシリアライザでシリアル化する正会員に帰属するメンバー間の通知にXMLライターを作っ:deploymentzone.com/2008/09/19/...
シミーワイツハンドラー

これは、すべての問題を完全にキャッチしているようには見えません。コンストラクターでIEqualityComparerを設定していますが、このコードではシリアル化されません。この辞書を拡張してこの情報を含める方法についてのアイデアはありますか?Typeオブジェクトを介してその情報を処理できますか?
ColinCren

回答:


27

別の大きな落とし穴:Webページ(ASP.NET)を介してXMLを出力する場合、Unicode Byte-Order Markを含めたくないでしょう。もちろん、BOMを使用する方法と使用しない方法はほぼ同じです。

BAD(BOMを含む):

XmlTextWriter wr = new XmlTextWriter(stream, new System.Text.Encoding.UTF8);

良い:

XmlTextWriter  wr = new XmlTextWriter(stream, new System.Text.UTF8Encoding(false))

明示的にfalseを渡して、BOMが不要であることを示すことができます。間に明確な、明白な違いに注意Encoding.UTF8してをUTF8Encoding

先頭の3つの追加のBOMバイトは(0xEFBBBF)または(239 187 191)です。

リファレンス:http : //chrislaco.com/blog/troubleshooting-common-problems-with-the-xmlserializer/


4
何かだけでなく、理由も教えていただければ、コメントはさらに役に立ちます。
ニール、

1
これは実際にはXMLのシリアル化とは関係ありません...それは単なるXmlTextWriterの問題です
Thomas Levesque

7
-1:質問とは関係ありませんXmlTextWriter。.NET2.0以降では使用しないでください。
ジョンサンダース

非常に役立つ参照リンク。ありがとう。
Anil Vangari

21

まだコメントはできませんので、Dr8kの投稿にコメントし、再度観察します。パブリックゲッター/セッタープロパティとして公開され、それらのプロパティを通じてシリアル化/非シリアル化されるプライベート変数。私はいつも私の古い仕事でそれをしました。

ただし、これらのプロパティにロジックがある場合はロジックが実行されるため、シリアル化の順序が実際に重要になることがあります。メンバーはコード内での順序によって暗黙的に順序付けられますが、特に別のオブジェクトを継承している場合は保証されません。それらを明示的に注文することは、後部の苦痛です。

過去にこれでやけどをしたことがあります。


17
フィールドの順序を明示的に設定する方法を検索しているときに、この投稿を見つけました。これは属性を使用して行われます:[XmlElementAttribute(Order = 1)] public int Field {...}欠点:属性は、クラスとそのすべての子孫のすべてのフィールドに指定する必要があります!IMOこれを投稿に追加する必要があります。
クリスティアンディアコネスク

15

メモリストリームからXML文字列にシリアル化する場合は、MemoryStream#GetBuffer()ではなくMemoryStream#ToArray()を使用してください。そうしないと、余分なバッファーが割り当てられるため、適切に非直列化されないジャンク文字が発生します。

http://msdn.microsoft.com/en-us/library/system.io.memorystream.getbuffer(VS.80).aspx


3
「バッファには未使用の可能性がある割り当てられたバイトが含まれていることに注意してください。たとえば、文字列「test」がMemoryStreamオブジェクトに書き込まれる場合、GetBufferから返されるバッファの長さは252バイトで、4ではなく256です。未使用。バッファ内のデータのみを取得するには、ToArrayメソッドを使用しますが、ToArrayはメモリ内にデータのコピーを作成します。 msdn.microsoft.com/en-us/library/...
realgt

たった今これを見た。もはやナンセンスのように聞こえません。
ジョンサンダース

これを聞いたことがないので、デバッグに役立ちます。
リッキー

10

シリアライザが、そのタイプとしてインターフェースを持つメンバー/プロパティに遭遇した場合、シリアライズしません。たとえば、次のコードはXMLにシリアル化されません。

public class ValuePair
{
    public ICompareable Value1 { get; set; }
    public ICompareable Value2 { get; set; }
}

これはシリアライズされますが:

public class ValuePair
{
    public object Value1 { get; set; }
    public object Value2 { get; set; }
}

「メンバーのタイプが解決されていません...」というメッセージが表示される例外が発生した場合は、これが原因である可能性があります。
カイル・クルル

9

IEnumerables<T>利回りリターンによって生成されたものはシリアル化できません。これは、コンパイラーがyield returnを実装する別のクラスを生成し、そのクラスがシリアライズ可能としてマークされていないためです。


これは、「その他」のシリアライゼーション、つまり[Serializable]属性に適用されます。ただし、これはXmlSerializerでも機能しません。
Tim Robinson、


8

読み取り専用プロパティをシリアル化することはできません。逆シリアル化を使用してXMLをオブジェクトに変換するつもりがない場合でも、ゲッターとセッターが必要です。

同じ理由で、インターフェイスを返すプロパティをシリアル化することはできません。デシリアライザはインスタンス化する具体的なクラスを認識しません。


1
実際には、セッターがなくてもコレクションプロパティをシリアル化できますが、逆シリアル化でアイテムを追加できるように、コンストラクターで初期化する必要があります
Thomas Levesque

7

ああ、これは良いことです。XMLシリアル化コードが生成されて別のDLLに配置されるので、シリアライザを破壊するコードに誤りがあっても、意味のあるエラーは発生しません。「s3d3fsdf.dllが見つかりません」のようなものです。いいね。


11
XML "Serializer Generator Tool(Sgen.exe)"を使用してそのDLLを事前に生成し、アプリケーションと共に展開できます。
huseyint 2008

6

パラメータなしのコンストラクタを持たないオブジェクトにシリアル化できません(そのコンストラクタに噛まれただけです)。

そして、何らかの理由で、次のプロパティからValueはシリアル化されますが、FullNameはシリアル化されません。

    public string FullName { get; set; }
    public double Value { get; set; }

理由を理解するために丸くなりませんでした。値を内部に変更しました...


4
パラメーターなしのコンストラクターは、プライベート/保護することができます。XMLシリアライザーにはこれで十分です。FullNameの問題は本当に奇妙で、発生してはなりません...
Max Galkin

@Yacoder:たぶんdouble?それだけではないのでdouble
abatishchev

FullNameはおそらくnullシリアライズされたため、XMLを生成しませんでした
Jesper

5

注意すべきもう1つのこと:「デフォルト」のXMLシリアル化を使用している場合、プライベート/保護されたクラスメンバーをシリアル化することはできません。

ただし、クラスにIXmlSerializableを実装するカスタムXMLシリアル化ロジックを指定して、必要な/必要なプライベートフィールドをシリアル化できます。

http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx


4

XMLシリアル化によって生成されたアセンブリが、それを使用しようとするコードと同じLoadコンテキストにない場合、次のような素晴らしいエラーが発生します。

System.InvalidOperationException: There was an error generating the XML document.
---System.InvalidCastException: Unable to cast object
of type 'MyNamespace.Settings' to type 'MyNamespace.Settings'. at
Microsoft.Xml.Serialization.GeneratedAssembly.
  XmlSerializationWriterSettings.Write3_Settings(Object o)

私のこの原因は、LoadFromコンテキストを使用してロードされたプラグインあり、Loadコンテキストを使用することに対して多くの欠点があります。その1つを追跡するのはかなり楽しいです。




4

配列、List<T>またはIEnumerable<T>サブクラスのインスタンスを含む配列をシリアル化する場合はTXmlArrayItemAttributeを使用して、使用されているすべてのサブタイプをリストする必要があります。そうしないと、System.InvalidOperationExceptionシリアル化するときに実行時に役に立たなくなります。

これはドキュメントの完全な例の一部です

public class Group
{  
   /* The XmlArrayItemAttribute allows the XmlSerializer to insert both the base 
      type (Employee) and derived type (Manager) into serialized arrays. */

   [XmlArrayItem(typeof(Manager)), XmlArrayItem(typeof(Employee))]
   public Employee[] Employees;

3

プライベート変数/プロパティは、XMLシリアル化のデフォルトメカニズムではシリアル化されませんが、バイナリシリアル化で使用されます。


2
はい、「デフォルト」のXMLシリアル化を使用している場合。クラスにIXmlSerializableを実装するカスタムXMLシリアル化ロジックを指定し、必要な/必要なプライベートフィールドをシリアル化できます。
Max Galkin

1
まあ、これは本当です。これを編集します。しかし、そのインターフェースを実装することは、私が覚えていることから、お尻の痛みのようなものです。
Charles Graham、

3

Obsolete属性でマークされたプロパティはシリアル化されません。私はDeprecated属性でテストしていませんが、同じように動作すると思います。


2

私はこれを本当に説明することはできませんが、これはシリアライズされないことがわかりました:

[XmlElement("item")]
public myClass[] item
{
    get { return this.privateList.ToArray(); }
}

しかし、これは:

[XmlElement("item")]
public List<myClass> item
{
    get { return this.privateList; }
}

また、memstreamにシリアル化する場合は、使用する前に0にシークすることをお勧めします。


再構築できないからだと思います。2番目の例では、item.Add()を呼び出して項目をリストに追加できます。最初はできません。
イリリット2008

18
使用:[XmlArray( "item")、XmlArrayItem( "myClass"、typeof(myClass))]
RvdK '16

1
乾杯!毎日何かを学ぶ
アナカタ09


2

XSDが置換グループを使用している場合、それを自動的に(逆)シリアル化できない可能性があります。このシナリオを処理するには、独自のシリアライザーを作成する必要があります。

例えば。

<xs:complexType name="MessageType" abstract="true">
    <xs:attributeGroup ref="commonMessageAttributes"/>
</xs:complexType>

<xs:element name="Message" type="MessageType"/>

<xs:element name="Envelope">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
            <xs:element ref="Message" minOccurs="0" maxOccurs="unbounded"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

<xs:element name="ExampleMessageA" substitutionGroup="Message">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
                <xs:attribute name="messageCode"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

<xs:element name="ExampleMessageB" substitutionGroup="Message">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
                <xs:attribute name="messageCode"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

この例では、エンベロープにメッセージを含めることができます。ただし、.NETのデフォルトのシリアライザーは、メッセージ、ExampleMessageA、およびExampleMessageBを区別しません。基本のMessageクラスとの間でのみシリアル化されます。


0

プライベート変数/プロパティは、XMLシリアル化ではシリアル化されませんが、バイナリシリアル化されます。

パブリックプロパティを介してプライベートメンバーを公開している場合にも、これが役立つと思います。プライベートメンバーはシリアル化されないため、パブリックメンバーはすべてnull値を参照しています。


本当じゃない。パブリックプロパティのセッターが呼び出され、おそらくプライベートメンバーが設定されます。
ジョンサンダース
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.