为什么XML-Serializable类需要无参数构造函数


173

我正在编写代码以进行Xml序列化。具有以下功能。

public static string SerializeToXml(object obj)
{
    XmlSerializer serializer = new XmlSerializer(obj.GetType());
    using (StringWriter writer = new StringWriter())
    {
        serializer.Serialize(writer, obj);
        return writer.ToString();
    }
}

如果参数是没有无参数构造函数的类的实例,则它将引发异常。

未处理的异常:System.InvalidOperationException:CSharpConsole.Foo无法序列化,因为它没有无参数的构造函数。在System.Xml.Serialization.TypeScope.GetTypeDesc(Type type,MemberInfo源,Boolean directReference,Boolean throwOnError)在System.Xml.Serialization.ModelScope.GetTypeModel(Type type, System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type,XmlRootAttribute root,String defaultNamespace)在System.Xml.Serialization.XmlSerializer..ctor(Type type,String defaultName space)在System.Xml.Serialization。 XmlSerializer..ctor(类型类型)

为什么必须有一个无参数的构造函数才能使xml序列化成功?

编辑:感谢cfeduke的回答。无参数构造函数可以是私有的或内部的。


1
如果您有兴趣,我找到了无需构造函数即可创建对象的方法(请参阅更新)-但这完全不会帮助XmlSerializer-它仍然需要它。也许对自定义代码有用。
Marc Gravell

1
XmlSerializer需要用于反序列化的默认无参数构造函数。
阿米特·库玛·戈什

Answers:


244

在对象的反序列化过程中,负责对对象进行反序列化的类将创建序列化类的实例,然后仅在获取要填充的实例之后,再填充序列化的字段和属性。

你可以让你的构造函数privateinternal如果你想,只是这么长时间,因为它是无参数。


1
哦,所以,我可以将无参数的ctor设为私有或内部,并且序列化仍然有效。感谢您的回答。
Morgan Cheng

2
是的,我经常这样做,尽管我已经接受了公共的无参数构造函数非常棒,因为它们允许您将“ new()”与泛型和新的初始化语法一起使用。对于带参数的构造函数,请使用静态工厂方法或构建器模式实现。
cfeduke

14
可访问性提示是一个很好的提示,但是您的解释对序列化没有任何意义。仅需要为反序列化创建对象。我可能会猜测类型检查代码内置在XmlSerializer构造函数中,因为一个实例可以同时使用两种方式。
Tomer Gabel,

7
@jwg一个例子是,当您将XML发送到某种Web服务并且对接收自己组件中的那些对象不感兴趣时​​。
Tomer Gabel

5
请记住,即使您使用无参数构造函数privateinternal,其值序列化的所有属性也必须具有publicsetter。
chrnola 2014年

75

这是的限制XmlSerializer。请注意,BinaryFormatter并且DataContractSerializer 不需要这样做-他们可以在ether之外创建未初始化的对象,并在反序列化期间将其初始化。

由于您使用XML,你可能会考虑使用DataContractSerializer和标记使用你的类[DataContract]/ [DataMember],但注意这改变的模式(例如,不存在等价物[XmlAttribute]-一切都变得元素)。

更新:如果您真的想知道,可以BinaryFormatter使用其他FormatterServices.GetUninitializedObject()方法创建对象而无需调用构造函数。可能很危险;我不建议经常使用它;-p另请参见MSDN上的评论:

因为该对象的新实例被初始化为零,并且没有运行构造函数,所以该对象可能不表示该对象认为有效的状态。仅当用户打算立即填充所有字段时,才应将当前方法用于反序列化。它不会创建未初始化的字符串,因为创建不可变类型的空实例没有用。

我有自己的序列化引擎,但我不打算使用它FormatterServices;我很想知道一个构造函数(任何构造函数)实际上已经执行过。


感谢您提供有关FormatterServices.GetUninitializedObject(Type)的技巧。:)
Omer van Kloeten 09年

6
h 原来我不听从自己的建议;protobuf网具有(任选地)允许FormatterServices用于使用年龄
马克Gravell

1
但是我不明白的是,在没有指定构造函数的情况下,编译器会创建一个公共的无参数构造函数。那么,为什么对于xml反序列化引擎来说还不够好呢?
toddmo'1

如果我想反序列化XML并使用其构造函数初始化某些对象(以便通过构造函数提供元素/属性),是否有任何方法可以实现?是否没有办法自定义序列化过程,以便它使用其构造函数来构建对象?
Shimmy Weitzhandler '17

1
@Shimmy nope; 不支持。还有就是 IXmlSerializable,但是:这种情况发生的构造,和b:这是非常丑陋的,很难得到正确的(特别是反序列化) -我强烈建议不要试图实现这一点,但:它不会让你使用构造
马克·格雷夫(Marc Gravell)

4

答案是:没有任何充分的理由。

与它的名称相反,XmlSerializer该类不仅用于序列化,而且还用于反序列化。它对您的类执行某些检查以确保它可以正常工作,并且其中一些检查仅与反序列化相关,但是无论如何它都会执行它们,因为它不知道您以后打算做什么。

您的课程未能通过的检查是仅与反序列化相关的检查之一。这是发生了什么:

  • 反序列化期间,XmlSerializer该类将需要创建您类型的实例。

  • 为了创建一个类型的实例,需要调用该类型的构造函数。

  • 如果未声明构造函数,则编译器已经提供了默认的无参数构造函数,但是,如果确实声明了构造函数,则这是唯一可用的构造函数。

  • 因此,如果您声明的构造函数接受参数,则实例化类的唯一方法是调用该接受参数的构造函数。

  • 但是,XmlSerializer除了无参数构造函数外,它无法调用任何构造函数,因为它不知道将哪些参数传递给接受参数的构造函数。因此,它将检查您的类是否具有无参数构造函数,并且由于没有,因此失败。

因此,如果XmlSerializer以仅执行与序列化相关的检查的方式编写了类,则您的类将通过,因为关于序列化的任何事情绝对不需要使无参数构造函数成为必需。

正如其他人已经指出的那样,对您的问题的快速解决方案是仅添加无参数构造函数。不幸的是,这也是一个肮脏的解决方案,因为这意味着您不能readonly从构造函数参数中初始化任何成员。

除了这一切,该XmlSerializer可能已经写在这样一种方式,允许类甚至反序列化没有参数构造函数。所要做的只是利用“工厂方法设计模式”(Wikipedia)。从外观上看,Microsoft认为对于DotNet程序员来说,这种设计模式太先进了,显然,不应将它们与这些东西混淆。因此,微软表示,DotNet程序员应该更好地坚持使用无参数构造函数。


1
大声笑你说,For no good reason whatsoever,然后继续说,XmlSerializer is not capable of invoking any constructor except a parameterless constructor, because it does not know what parameters to pass to constructors that accept parameters.如果它不知道要传递给构造函数的参数,那么它将如何知道要传递给工厂的参数呢?还是使用哪个工厂?我无法想象该工具更简单易用-您想要反序列化一个类,然后让反序列化器创建一个默认实例,然后填充您标记的每个字段。简单。
Chuck

@Chuck,您可能想问另一个StackOverflow问题:“工厂方法如何通过构造函数参数帮助我”,让我知道,我将为您解答。
Mike Nakis

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.