.NET XML序列化陷阱?[关闭]


121

在进行我想分享的C#XML序列化时,遇到了一些陷阱:


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序列化陷阱吗?


看着更多的陷阱笑,你也许能够帮助我:stackoverflow.com/questions/2663836/...
摆振Weitzhandler

1
此外,你会想看看查尔斯Feduke的实现serialzable字典的,他所做的XML作家归属成员正式成员之间的通知由默认的序列进行序列化:deploymentzone.com/2008/09/19/...
Shimmy Weitzhandler

这似乎并不能完全解决所有问题。我在构造函数中设置IEqualityComparer,但是在此代码中未进行序列化。关于如何扩展此词典以包含此信息的任何想法?可以通过Type对象处理该信息吗?
ColinCren 2011年

Answers:


27

另一个巨大的难题:通过网页(ASP.NET)输出XML时,您不希望包含Unicode Byte-Order Mark。当然,使用或不使用BOM的方式几乎相同:

不良(包括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

开头的三个额外BOM字节为(0xEFBBBF)或(239 187 191)。

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


4
如果您不仅告诉我们什么,还告诉我们原因,您的评论将更加有用。
尼尔

1
这与XML序列化并没有真正的关系……这只是一个XmlTextWriter问题
Thomas Levesque

7
-1:与问题无关,您不应该XmlTextWriter在.NET 2.0或更高版本中使用。
约翰·桑德斯

非常有用的参考链接。谢谢。
Anil Vangari 2012年

21

我还不能发表评论,所以我将对Dr8k的帖子发表评论,然后再进行观察。私有变量公开为公共getter / setter属性,并通过这些属性进行序列化/反序列化。我们是在以前的旧工作中做到这一点的。

但是要注意的一件事是,如果这些属性中有任何逻辑,则逻辑会运行,因此有时,序列化顺序实际上很重要。成员是按代码中的顺序进行隐式排序的,但是并不能保证,尤其是当您继承另一个对象时。明确订购它们是后方的痛苦。

过去我一直为此感到疲倦。


17
我在寻找明确设置字段顺序的方法时找到了这篇文章。这是通过属性完成的:[XmlElementAttribute(Order = 1)] public int Field {...}缺点:必须为类及其所有后代中的所有字段指定属性!IMO您应该将此添加到您的帖子中。
Cristian Diaconescu,

15

从内存流序列化为XML字符串时,请确保使用MemoryStream#ToArray()而不是MemoryStream#GetBuffer(),否则您将得到无法正确反序列化的垃圾字符(由于分配了额外的缓冲区)。

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


3
直接从文档“请注意,缓冲区包含分配的字节,这些字节可能未使用。例如,如果将字符串” test”写入MemoryStream对象,则从GetBuffer返回的缓冲区的长度为256,而不是4,即252字节要仅获取缓冲区中的数据,请使用ToArray方法;但是,ToArray将在内存中创建数据的副本。” msdn.microsoft.com/en-us/library/…–
realgt

只是现在才看到这一点。不再听起来像胡话。
约翰·桑德斯

以前从未听说过,这对调试很有帮助。
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; }
}

如果您收到一条异常消息“类型未为成员解析...”,则可能是这种情况。
凯尔·

9

IEnumerables<T>通过收益率回报生成的序列不可序列化。这是因为编译器会生成一个单独的类来实现收益回报,并且该类未标记为可序列化。


这适用于“其他”序列化,即[Serializable]属性。但是,这对于XmlSerializer也不起作用。
蒂姆·罗宾逊


8

您不能序列化只读属性。即使您从未打算使用反序列化将XML转换为对象,也必须具有getter和setter。

出于同样的原因,您不能序列化返回接口的属性:解串器将不知道要实例化的具体类。


1
其实你可以序列化,即使它没有制定者集合属性,但它在构造函数初始化,使反序列化可以添加项目
托马斯Levesque的

7

哦,这是一个很好的方法:由于XML序列化代码已生成并放置在单独的DLL中,所以当代码中出现错误而使序列化程序中断时,您将不会收到任何有意义的错误。就像“无法找到s3d3fsdf.dll”之类的东西。真好


11
您可以通过使用XML“ Serializer Generator工具(Sgen.exe)”提前生成该DLL,并与您的应用程序一起部署。
huseyint

6

无法序列化没有无参数构造函数的对象(只是被该对象咬住了)。

并且由于某些原因,从以下属性中,值将被序列化,但不会被FullName序列化:

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

我从来没有想过为什么,我只是将Value更改为Internal ...


4
无参数构造函数可以是私有/受保护的。XML序列化器就足够了。FullName的问题确实很奇怪,不应该发生……
Max Galkin 2009年

@Yacoder:也许是因为不是,double?而仅仅是double
abatishchev 2011年

FullName可能null并且因此不会在序列化时生成任何XML
Jesper 2015年


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上下文相比有许多缺点。跟踪下来很有趣。




4

如果您尝试序列化一个数组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;

3

专用变量/属性不在XML序列化的默认机制中序列化,而是在二进制序列化中。


2
是的,如果您使用的是“默认” XML序列化。您可以在类中指定实现IXmlSerializable的自定义XML序列化逻辑,并序列化您需要/想要的任何私有字段。
Max Galkin

1
好吧,这是真的。我将对此进行编辑。但是从我记得的角度来看,实现该接口有点麻烦。
查尔斯·格雷厄姆

3

标有该Obsolete属性的属性不会序列化。我尚未测试过Deprecated属性,但我认为它的作用方式相同。


2

我不能真正解释这一点,但是我发现它不会序列化:

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

但这将:

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

同样值得注意的是,如果要序列化到内存流,则可能需要先使用0。


我认为这是因为它无法重建它。在第二个示例中,它可以调用item.Add()将项目添加到列表中。首先它做不到。
ilitirit

18
使用:[XmlArray(“ item”),XmlArrayItem(“ myClass”,typeof(myClass))]
RvdK

1
为此加油!每天学习一些东西
annakata


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的默认序列化程序无法区分Message,ExampleMessageA和ExampleMessageB。它只会与基本Message类之间进行序列化。


0

私有变量/属性不在XML序列化中序列化,而在二进制序列化中。

如果您通过公共属性公开私有成员,我相信这也会为您带来好处-私有成员不会序列化,因此公共成员都引用空值。


这不是真的。公共财产的设定者将被称为,并且可能会设定私人成员。
约翰·桑德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.