.NET serialisasi XML gotchas? [Tutup]

121

Saya mengalami beberapa masalah saat melakukan serialisasi C # XML yang saya pikir akan saya bagikan:


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();
        }
    }
}

Adakah gotchas Serialisasi XML lain di luar sana?

kurious
sumber
Carilah lebih banyak gotcha lol, Anda mungkin bisa membantu saya: stackoverflow.com/questions/2663836/…
Shimmy Weitzhandler
1
Juga, Anda akan ingin melihat implementasi Charles Feduke dari kamus yang dapat bersambung, dia membuat penulis xml untuk memberi tahu antara anggota yang dapat diatribusikan ke anggota biasa untuk diserialkan dengan penyambung default: deploymentzone.com/2008/09/19/…
Shimmy Weitzhandler
Ini sepertinya tidak cukup menangkap semua gotcha. Saya mengatur IEqualityComparer di konstruktor, tetapi itu tidak bisa diserialkan dalam kode ini. Adakah ide tentang bagaimana memperluas kamus ini untuk memasukkan sedikit informasi ini? dapatkah informasi itu ditangani melalui objek Type?
ColinCren

Jawaban:

27

Gotcha besar lainnya: saat mengeluarkan XML melalui halaman web (ASP.NET), Anda tidak ingin menyertakan Unicode Byte-Order Mark . Tentunya cara menggunakan atau tidak menggunakan BOM hampir sama:

BURUK (termasuk BOM):

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

BAIK:

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

Anda dapat secara eksplisit memberikan false untuk menunjukkan bahwa Anda tidak menginginkan BOM. Perhatikan perbedaan yang jelas dan nyata antara Encoding.UTF8dan UTF8Encoding.

Tiga BOM Bytes tambahan di awal adalah (0xEFBBBF) atau (239 187 191).

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

Kalid
sumber
4
Komentar Anda akan lebih berguna jika Anda tidak hanya memberi tahu kami apa, tetapi mengapa.
Neil
1
Ini tidak benar-benar terkait dengan serialisasi XML ... ini hanya masalah XmlTextWriter
Thomas Levesque
7
-1: Tidak terkait dengan pertanyaan, dan Anda tidak boleh menggunakan XmlTextWriterdi .NET 2.0 atau lebih tinggi.
John Saunders
Tautan referensi yang sangat membantu. Terima kasih.
Anil Vangari
21

Saya belum bisa memberikan komentar, jadi saya akan mengomentari postingan Dr8k dan melakukan observasi lagi. Variabel privat yang diekspos sebagai properti pengambil / penyetel publik, dan mendapatkan serial / deserialisasi seperti itu melalui properti tersebut. Kami melakukannya di pekerjaan lama saya setiap saat.

Satu hal yang perlu diperhatikan adalah bahwa jika Anda memiliki logika di properti tersebut, logika tersebut dijalankan, jadi terkadang, urutan serialisasi sebenarnya penting. Anggotanya adalah implisit diurutkan berdasarkan bagaimana mereka diurutkan dalam kode, tetapi tidak ada jaminan, terutama saat Anda mewarisi objek lain. Memesannya secara eksplisit adalah menyebalkan.

Saya pernah terbakar oleh ini di masa lalu.

Charles Graham
sumber
17
Saya menemukan posting ini saat mencari cara untuk mengatur urutan bidang secara eksplisit. Ini dilakukan dengan atribut: [XmlElementAttribute (Order = 1)] public int Field {...} Kelemahan: atribut harus ditentukan untuk SEMUA field di kelas dan semua turunannya! IMO Anda harus menambahkan ini ke posting Anda.
Cristian Diaconescu
15

Saat membuat serialisasi menjadi string XML dari aliran memori, pastikan untuk menggunakan MemoryStream # ToArray () sebagai ganti MemoryStream # GetBuffer () atau Anda akan berakhir dengan karakter sampah yang tidak akan melakukan deserialisasi dengan benar (karena buffer tambahan dialokasikan).

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

realgt
sumber
3
langsung dari dokumen "Perhatikan bahwa buffer berisi byte yang dialokasikan yang mungkin tidak digunakan. Misalnya, jika string" test "ditulis ke dalam objek MemoryStream, panjang buffer yang dikembalikan dari GetBuffer adalah 256, bukan 4, dengan 252 byte tidak terpakai. Untuk mendapatkan hanya data di buffer, gunakan metode ToArray; namun, ToArray membuat salinan data di memori. " msdn.microsoft.com/en-us/library/…
realgt
baru saja melihat ini. Tidak lagi terdengar seperti omong kosong.
John Saunders
Belum pernah mendengar ini sebelumnya, yang sangat membantu dalam debugging.
Ricky
10

Jika serializer menemukan anggota / properti yang memiliki antarmuka sebagai tipenya, itu tidak akan membuat serial. Misalnya, berikut ini tidak akan membuat serial ke XML:

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

Meskipun ini akan membuat serial:

public class ValuePair
{
    public object Value1 { get; set; }
    public object Value2 { get; set; }
}
Allon Guralnek
sumber
Jika Anda mendapatkan pengecualian dengan pesan "Jenis tidak diselesaikan untuk anggota ...", ini mungkin yang terjadi.
Kyle Krull
9

IEnumerables<T>yang dihasilkan melalui pengembalian hasil tidak dapat diserialkan. Ini karena kompilator membuat kelas terpisah untuk mengimplementasikan pengembalian hasil dan kelas itu tidak ditandai sebagai dapat diserialkan.

abatishchev
sumber
Ini berlaku untuk serialisasi 'lainnya', yaitu atribut [Serializable]. Ini juga tidak berfungsi untuk XmlSerializer.
Tim Robinson
8

Anda tidak dapat membuat serial properti read-only. Anda harus memiliki pengambil dan penyetel, bahkan jika Anda tidak pernah berniat menggunakan deserialisasi untuk mengubah XML menjadi objek.

Untuk alasan yang sama, Anda tidak dapat membuat serial properti yang mengembalikan antarmuka: deserializer tidak akan tahu kelas konkret apa yang harus dibuat.

Tim Robinson
sumber
1
Sebenarnya Anda dapat membuat serial properti collection meskipun tidak memiliki penyetel, tetapi harus diinisialisasi dalam konstruktor sehingga deserialisasi dapat menambahkan item ke dalamnya
Thomas Levesque
7

Oh, ini bagus: karena kode serialisasi XML dibuat dan ditempatkan di DLL terpisah, Anda tidak akan mendapatkan kesalahan yang berarti jika ada kesalahan dalam kode Anda yang merusak serializer. Hanya sesuatu seperti "tidak dapat menemukan s3d3fsdf.dll". Bagus.

Eric Z Beard
sumber
11
Anda dapat membuat DLL tersebut sebelumnya dengan menggunakan XML "Serializer Generator Tool (Sgen.exe)" dan menerapkan dengan aplikasi Anda.
huseyint
6

Tidak dapat membuat serial objek yang tidak memiliki konstruktor tanpa parameter (baru saja digigit oleh yang itu).

Dan untuk beberapa alasan, dari properti berikut ini, Value menjadi serial, tetapi tidak FullName:

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

Saya tidak pernah sempat memikirkan mengapa, saya baru saja mengubah Nilai menjadi internal ...

Benjol
sumber
4
Konstruktor tanpa parameter dapat bersifat pribadi / dilindungi. Ini akan cukup untuk serializer XML. Masalah dengan FullName benar-benar aneh, seharusnya tidak terjadi ...
Max Galkin
@Yacoder: Mungkin karena bukan double?tapi hanya double?
abatishchev
FullName mungkin nulldan oleh karena itu tidak akan menghasilkan XML apa pun saat diserialkan
Jesper
4

Jika rakitan yang dihasilkan Serialisasi XML Anda tidak dalam konteks Muat yang sama dengan kode yang mencoba menggunakannya, Anda akan mengalami kesalahan hebat seperti:

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)

Penyebabnya bagi saya adalah plugin yang dimuat menggunakan konteks LoadFrom yang memiliki banyak kelemahan menggunakan konteks Load. Cukup menyenangkan melacak yang satu itu.

pengguna7116
sumber
4

Jika Anda mencoba membuat serial array List<T>,, atau IEnumerable<T>yang berisi instance subclass dariT , Anda perlu menggunakan XmlArrayItemAttribute ke daftar semua subtipe yang digunakan. Jika tidak, Anda akan mendapatkan error System.InvalidOperationExceptionsaat runtime saat Anda membuat serial.

Berikut adalah bagian dari contoh lengkap dari dokumentasi

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;
MarkJ
sumber
3

Variabel / properti pribadi tidak diserialkan dalam mekanisme default untuk serialisasi XML, tetapi dalam serialisasi biner.

Charles Graham
sumber
2
Ya, jika Anda menggunakan serialisasi XML "default". Anda dapat menentukan logika serialisasi XML khusus yang menerapkan IXmlSerializable di kelas Anda dan membuat serialisasi bidang pribadi apa pun yang Anda perlukan / inginkan.
Max Galkin
1
Ini benar. Saya akan mengedit ini. Tapi menerapkan antarmuka itu agak menyebalkan dari apa yang saya ingat.
Charles Graham
3

Properti yang ditandai dengan Obsoleteatribut tidak berseri. Saya belum menguji dengan Deprecatedatribut tetapi saya berasumsi itu akan bertindak dengan cara yang sama.

James Hulse
sumber
2

Saya tidak bisa menjelaskan yang satu ini, tetapi saya menemukan ini tidak akan membuat serial:

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

tapi ini akan:

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

Dan juga perlu dicatat bahwa jika Anda melakukan serialisasi ke sebuah memstream, Anda mungkin ingin mencari 0 sebelum Anda menggunakannya.

annakata
sumber
Saya pikir itu karena tidak dapat membangunnya kembali. Dalam contoh kedua itu bisa memanggil item.Add () untuk menambahkan item ke Daftar. Itu tidak bisa dilakukan pada awalnya.
ilitirit
18
Gunakan: [XmlArray ("item"), XmlArrayItem ("myClass", typeof (myClass))]
RvdK
1
bersorak untuk itu! belajar sesuatu setiap hari
annakata
2

Berhati-hatilah saat melakukan serialisasi tanpa serialisasi eksplisit, ini dapat menyebabkan penundaan saat .Net membangunnya. Saya menemukan ini baru-baru ini saat menserialisasikan RSAParameters .

Keith
sumber
2

Jika XSD Anda menggunakan grup substitusi, maka kemungkinan Anda tidak dapat (de) membuat serialisasi secara otomatis. Anda harus membuat pembuat serial Anda sendiri untuk menangani skenario ini.

Misalnya.

<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>

Dalam contoh ini, Amplop dapat berisi Pesan. Namun, serializer default .NET tidak membedakan antara Message, ExampleMessageA dan ExampleMessageB. Ini hanya akan membuat serial ke dan dari kelas Message dasar.

ilitirit
sumber
0

Variabel / properti privat tidak diserialisasi dalam serialisasi XML, tetapi dalam serialisasi biner.

Saya percaya ini juga membuat Anda jika Anda mengekspos anggota privat melalui properti publik - anggota privat tidak diserialkan sehingga anggota publik semuanya mereferensikan nilai nol.

Dr8k
sumber
Ini tidak benar. Penyetel properti publik akan dipanggil, dan mungkin, akan menetapkan anggota pribadi.
John Saunders