web-dev-qa-db-ja.com

.NET XMLシリアル化の落とし穴?

私が共有したいと思った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シリアル化の落とし穴がありますか?

120
Kalid

もう1つの大きな落とし穴:Webページ(ASP.NET)を介してXMLを出力する場合、 nicodeバイトオーダーマーク を含めたくありません。もちろん、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.UTF8UTF8Encodingの明確で明らかな違いに注目してください。

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

参照: http://chrislaco.com/blog/troubleshooting-common-problems-with-the-xmlserializer/

27
Kalid

まだコメントできないので、Dr8kの投稿にコメントして、別の観察をします。パブリックgetter/setterプロパティとして公開され、それらのプロパティを介してシリアル化/非シリアル化されるプライベート変数。昔の仕事でやったんだ。

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

私はこれで過去に焼かれました。

21
Charles Graham

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

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

15
realgt

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

10
bwillard

シリアライザーが、型としてインターフェイスを持つメンバー/プロパティを検出した場合、シリアル化されません。たとえば、次は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; }
}
10
Allon Guralnek

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

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

8
Tim Robinson

ああ、これは良い方法です。XMLシリアル化コードが生成され、別のDLLに配置されるため、シリアライザーを壊すコードに間違いがある場合、意味のあるエラーは発生しません。 「s3d3fsdf.dllを見つけることができません」のようなものです。いいね.

7
Eric Z Beard

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

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

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

私はなぜ価値を内部に変更したのかを理解することができませんでした...

6
Benjol

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

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

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

5
Max Galkin

Color型やFont型のオブジェクトをシリアル化する際に問題が発生する場合があります。

ここに私が助けたアドバイスがあります:

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

http://www.codeproject.com/KB/cs/GenericXmlSerializition.aspx

4
Max Galkin

高度なXMLスキーマ定義言語属性のバインディングサポート 」を参照してください。XMLシリアライザーでサポートされるものの詳細、およびサポートされるXSD機能がサポートされる方法の詳細については。

4
John Saunders

配列をシリアル化しようとすると、List<T>、 または IEnumerable<T>には、Tサブクラスのインスタンスが含まれます。すべてのリストを表示するには、 XmlArrayItemAttribute を使用する必要があります使用されているサブタイプ。そうしないと、役に立たない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;
4
MarkJ

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 context を使用してロードされたプラグインであり、Loadコンテキストの使用には多くの欠点があります。その1つを追跡するかなり楽しい。

4
user7116

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

3
James Hulse

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

3
Charles Graham

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のデフォルトのシリアライザーは、Message、ExampleMessageA、およびExampleMessageBを区別しません。基本Messageクラスとの間でのみシリアル化されます。

2
ilitirit

明示的なシリアル化を行わずに型をシリアル化する場合は注意してください。Netがそれらを構築する間に遅延が発生する可能性があります。最近これを発見しました RSAParametersのシリアル化中

2
Keith

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

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

しかし、これは:

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

また、memstreamにシリアル化する場合は、使用する前に0にシークすることもできます。

2
annakata

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

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

0
Dr8k