.NET XML serialization gotchas? [ปิด]


121

ฉันเจอปัญหาบางอย่างเมื่อทำการอนุกรม C # XML ที่ฉันคิดว่าจะแบ่งปัน:

  • คุณไม่สามารถจัดลำดับรายการที่อ่านอย่างเดียว (เช่น KeyValuePairs)
  • คุณไม่สามารถจัดลำดับพจนานุกรมทั่วไปได้ ลองใช้คลาส wrapper นี้แทน (จาก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 อื่น ๆ มีอะไรบ้าง?


มองหา gotchas เพิ่มเติม lol คุณอาจจะช่วยฉันได้: stackoverflow.com/questions/2663836/…
Shimmy Weitzhandler

1
นอกจากนี้คุณจะต้องดูการใช้งานพจนานุกรม serialzable ของ Charles Feduke เขาทำให้ผู้เขียน xml สังเกตเห็นระหว่างสมาชิกที่เกี่ยวข้องกับสมาชิกทั่วไปที่จะได้รับการจัดลำดับโดย serializer เริ่มต้น: deploymentzone.com/2008/09/19/…
Shimmy Weitzhandler

ดูเหมือนว่ามันจะไม่ดึงดูด gotchas ทั้งหมด ฉันกำลังตั้งค่า IEqualityComparer ในตัวสร้าง แต่ไม่ได้รับการต่ออนุกรมในโค้ดนี้ มีแนวคิดในการขยายพจนานุกรมนี้อย่างไรเพื่อรวมข้อมูลเล็กน้อย ข้อมูลนั้นสามารถจัดการผ่านวัตถุ Type ได้หรือไม่?
ColinCren

คำตอบ:


27

อีก gotcha ใหญ่เมื่อ outputting XML ผ่านทางหน้าเว็บ (ASP.NET), คุณไม่ต้องการที่จะรวมUnicode ลำดับไบต์มาร์ค แน่นอนว่าวิธีการใช้หรือไม่ใช้ BOM นั้นเกือบจะเหมือนกัน:

BAD (รวมถึง BOM):

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

ดี:

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

คุณสามารถส่งเท็จอย่างชัดเจนเพื่อระบุว่าคุณไม่ต้องการ BOM สังเกตที่ชัดเจนแตกต่างที่ชัดเจนระหว่างและEncoding.UTF8UTF8Encoding

ไบต์ BOM เพิ่มเติมสามรายการที่จุดเริ่มต้นคือ (0xEFBBBF) หรือ (239187191)

อ้างอิง: http://chrislaco.com/blog/troubleshooting-common-pro issues-with-the-xmlserializer/


4
ความคิดเห็นของคุณจะมีประโยชน์มากกว่านี้หากคุณจะบอกเราไม่ใช่แค่ว่าอะไร แต่ทำไม
Neil

1
สิ่งนี้ไม่เกี่ยวข้องกับการทำให้เป็นอนุกรม XML จริงๆ ... เป็นเพียงปัญหา XmlTextWriter
Thomas Levesque

7
-1: ไม่เกี่ยวข้องกับคำถามและคุณไม่ควรใช้XmlTextWriterใน. NET 2.0 ขึ้นไป
John Saunders

ลิงค์อ้างอิงที่เป็นประโยชน์มาก ขอบคุณ
Anil Vangari

21

ฉันยังแสดงความคิดเห็นไม่ได้ดังนั้นฉันจะแสดงความคิดเห็นในโพสต์ของ Dr8k และสังเกตอีกครั้ง ตัวแปรส่วนตัวที่เปิดเผยเป็นคุณสมบัติ getter / setter สาธารณะและได้รับ serialized / deserialized ผ่านคุณสมบัติเหล่านั้น เราทำที่งานเก่าของฉันตลอดเวลา

สิ่งหนึ่งที่ควรทราบก็คือถ้าคุณมีตรรกะใด ๆ ในคุณสมบัติเหล่านั้นตรรกะจะทำงานดังนั้นบางครั้งลำดับของการทำให้เป็นอนุกรมก็มีความสำคัญ สมาชิกได้รับการจัดลำดับโดยปริยายตามวิธีการเรียงลำดับในโค้ด แต่ไม่มีการรับประกันใด ๆ โดยเฉพาะอย่างยิ่งเมื่อคุณรับช่วงอ็อบเจกต์อื่น การสั่งให้พวกเขาอย่างชัดเจนคืออาการปวดหลัง

ฉันเคยถูกไฟไหม้ในอดีต


17
ฉันพบโพสต์นี้ในขณะที่ค้นหาวิธีตั้งค่าลำดับของฟิลด์อย่างชัดเจน สิ่งนี้ทำได้โดยใช้แอตทริบิวต์: [XmlElementAttribute (Order = 1)] public int Field {... } Downside: ต้องระบุแอตทริบิวต์สำหรับช่องทั้งหมดในคลาสและลำดับถัดไปทั้งหมด! IMO คุณควรเพิ่มสิ่งนี้ในโพสต์ของคุณ
Cristian Diaconescu

15

เมื่อทำให้อนุกรมเป็นสตริง XML จากสตรีมหน่วยความจำอย่าลืมใช้ MemoryStream # ToArray () แทน MemoryStream # GetBuffer () มิฉะนั้นคุณจะลงเอยด้วยอักขระขยะที่ไม่สามารถแยกส่วนออกได้อย่างถูกต้อง (เนื่องจากมีการจัดสรรบัฟเฟอร์เพิ่มเติม)

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


3
ตรงจากเอกสาร "โปรดทราบว่าบัฟเฟอร์ประกอบด้วยไบต์ที่จัดสรรซึ่งอาจไม่ได้ใช้ตัวอย่างเช่นถ้าสตริง" test "ถูกเขียนลงในอ็อบเจ็กต์ MemoryStream ความยาวของบัฟเฟอร์ที่ส่งคืนจาก GetBuffer คือ 256 ไม่ใช่ 4 โดยมี 252 ไบต์ ไม่ได้ใช้หากต้องการรับเฉพาะข้อมูลในบัฟเฟอร์ให้ใช้วิธี ToArray อย่างไรก็ตาม ToArray จะสร้างสำเนาของข้อมูลในหน่วยความจำ " msdn.microsoft.com/en-us/library/…
realgt

ตอนนี้เพิ่งเห็นสิ่งนี้ ไม่ฟังดูเหมือนไร้สาระอีกต่อไป
John Saunders

ไม่เคยได้ยินเรื่องนี้มาก่อนซึ่งมีประโยชน์ในการดีบัก
Ricky

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

หากคุณได้รับข้อยกเว้นพร้อมข้อความ "ประเภทไม่ได้รับการแก้ไขสำหรับสมาชิก ... " นี่อาจเป็นสิ่งที่เกิดขึ้น
Kyle Krull

9

IEnumerables<T>ที่สร้างขึ้นจากผลตอบแทนตอบแทนไม่สามารถต่ออนุกรมได้ เนื่องจากคอมไพลเลอร์สร้างคลาสแยกต่างหากเพื่อใช้ผลตอบแทนและคลาสนั้นไม่ได้ถูกทำเครื่องหมายว่าเป็นอนุกรม


สิ่งนี้ใช้กับการทำให้เป็นอนุกรม "อื่น ๆ " นั่นคือแอตทริบิวต์ [ต่อเนื่องได้] สิ่งนี้ใช้ไม่ได้กับ XmlSerializer เช่นกัน
Tim Robinson


8

คุณไม่สามารถทำให้เป็นอนุกรมคุณสมบัติอ่านอย่างเดียว คุณต้องมี getter และ setter แม้ว่าคุณจะไม่เคยตั้งใจที่จะใช้ deserialization เพื่อเปลี่ยน XML ให้เป็นอ็อบเจกต์

ด้วยเหตุผลเดียวกันคุณจึงไม่สามารถทำให้เป็นอนุกรมคุณสมบัติที่ส่งคืนอินเทอร์เฟซได้: deserializer จะไม่รู้ว่าจะสร้างคลาสคอนกรีตใด


1
จริงๆแล้วคุณสามารถทำให้คุณสมบัติคอลเลกชันเป็นอนุกรมได้แม้ว่าจะไม่มีตัวตั้งค่า แต่จะต้องมีการเตรียมใช้งานในตัวสร้างเพื่อให้ deserialization สามารถเพิ่มรายการเข้าไปได้
Thomas Levesque

7

นี่เป็นสิ่งที่ดี: เนื่องจากรหัสซีเรียลไลเซชัน XML ถูกสร้างและวางไว้ใน DLL แยกต่างหากคุณจะไม่ได้รับข้อผิดพลาดใด ๆ ที่มีความหมายเมื่อมีข้อผิดพลาดในรหัสของคุณที่ทำให้ซีเรียลไลเซอร์เสียหาย บางอย่างเช่น "ไม่พบ s3d3fsdf.dll" ดี


11
คุณสามารถสร้าง DLL นั้นล่วงหน้าได้โดยใช้ XML "Serializer Generator Tool (Sgen.exe)" และปรับใช้กับแอปพลิเคชันของคุณ
huseyint

6

ไม่สามารถทำให้เป็นอนุกรมของวัตถุที่ไม่มีตัวกำหนดค่าพารามิเตอร์ (เพิ่งถูกกัดโดยวัตถุนั้น)

และด้วยเหตุผลบางประการจากคุณสมบัติต่อไปนี้ค่าจะถูกทำให้เป็นอนุกรม แต่ไม่ใช่ FullName:

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

ฉันไม่เคยคิดเลยว่าทำไมฉันถึงเปลี่ยน Value เป็น Internal ...


4
ตัวสร้างแบบไม่มีพารามิเตอร์สามารถเป็นแบบส่วนตัว / ป้องกันได้ จะเพียงพอสำหรับ XML serializer ปัญหาเกี่ยวกับ FullName เป็นเรื่องแปลกจริงๆไม่ควรเกิดขึ้น ...
Max Galkin

@Yacoder: อาจเป็นเพราะไม่ใช่double?แต่แค่double?
abatishchev

FullName น่าจะเป็นnullและจะไม่สร้าง XML ใด ๆ เมื่อทำให้เป็นอนุกรม
Jesper

5

สิ่งที่ควรทราบอีกประการหนึ่ง: คุณไม่สามารถจัดลำดับสมาชิกคลาสส่วนตัว / ที่มีการป้องกันได้หากคุณใช้การทำให้เป็นอนุกรม XML "ค่าเริ่มต้น"

แต่คุณสามารถระบุลอจิกการจัดลำดับ XML แบบกำหนดเองโดยใช้ IXmlSerializable ในคลาสของคุณและจัดลำดับฟิลด์ส่วนตัวที่คุณต้องการ / ต้องการ

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


4

หากแอสเซมบลีที่สร้างอนุกรม XML ของคุณไม่ได้อยู่ในบริบทการโหลดเดียวกันกับโค้ดที่พยายามใช้งานคุณจะพบข้อผิดพลาดที่น่ากลัวเช่น:

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ซึ่งมีข้อเสียมากมายในการใช้บริบทโหลด ค่อนข้างสนุกกับการติดตามสิ่งนั้นลง



4

โปรดดู " การสนับสนุนการเชื่อมโยงแอตทริบิวต์ภาษานิยามสคีมาขั้นสูง " สำหรับรายละเอียดเกี่ยวกับสิ่งที่รองรับโดย XML Serializer และสำหรับรายละเอียดเกี่ยวกับวิธีการรองรับคุณลักษณะ XSD ที่รองรับ


4

ถ้าคุณพยายามที่จะเป็นอันดับอาร์เรย์เป็นList<T>หรือIEnumerable<T>ซึ่งมีกรณีของsubclassesของTคุณจะต้องใช้XmlArrayItemAttribute to list เชื้อทั้งหมดที่ถูกนำมาใช้ มิฉะนั้นคุณจะไม่ได้รับความช่วยเหลือ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 "เริ่มต้น" คุณสามารถระบุลอจิกการทำให้เป็นอนุกรม XML แบบกำหนดเองโดยใช้ IXmlSerializable ในคลาสของคุณและจัดลำดับฟิลด์ส่วนตัวที่คุณต้องการ / ต้องการ
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 ก่อนใช้งาน


ฉันคิดว่ามันเป็นเพราะมันไม่สามารถสร้างมันขึ้นมาใหม่ได้ ในตัวอย่าง secondle สามารถเรียกรายการได้เพิ่ม () เพื่อเพิ่มรายการในรายการ มันไม่สามารถทำได้ในครั้งแรก
ilitirit

18
ใช้: [XmlArray ("item"), XmlArrayItem ("myClass", typeof (myClass))]
RvdK

1
ไชโย! เรียนรู้บางสิ่งทุกวัน
annakata

2

โปรดระวังประเภทการต่ออนุกรมโดยไม่ต้องทำให้เป็นอนุกรมอย่างชัดเจนอาจส่งผลให้เกิดความล่าช้าในขณะที่. Net สร้างขึ้น ผมค้นพบเมื่อเร็ว ๆ นี้RSAParameters ขณะ serialising


2

หาก XSD ของคุณใช้ประโยชน์จากกลุ่มการแทนที่มีโอกาสที่คุณจะไม่สามารถ (de) ทำให้เป็นอนุกรมโดยอัตโนมัติ คุณจะต้องเขียนซีเรียลไลเซอร์ของคุณเองเพื่อจัดการกับสถานการณ์นี้

เช่น.

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

ในตัวอย่างนี้ซองจดหมายสามารถมีข้อความได้ อย่างไรก็ตาม Serializer เริ่มต้นของ. NET ไม่ได้แยกความแตกต่างระหว่าง Message, ExampleMessageA และ ExampleMessageB มันจะซีเรียลไลซ์และจากคลาสข้อความพื้นฐานเท่านั้น


0

ตัวแปร / คุณสมบัติส่วนตัวไม่ได้เป็นอนุกรมในการทำให้เป็นอนุกรม XML แต่อยู่ในอนุกรมไบนารี

ฉันเชื่อว่าสิ่งนี้จะทำให้คุณได้รับเช่นกันหากคุณเปิดเผยสมาชิกส่วนตัวผ่านคุณสมบัติสาธารณะ - สมาชิกส่วนตัวไม่ได้รับการจัดลำดับดังนั้นสมาชิกสาธารณะจึงอ้างอิงค่า null ทั้งหมด


นี่ไม่เป็นความจริง. ผู้ตั้งค่าทรัพย์สินสาธารณะจะถูกเรียกและคงจะตั้งสมาชิกส่วนตัว
John Saunders
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.