Почему классу 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.TypeDesc.CheckSupported () в System.Xml.Serialization.TypeScope.GetTypeDesc (Тип тип, MemberInfo sourc e, логический directReference, Boolean throwOnError) в System.Xml.Serialization.ModelS TypeMetTepeTetGet Прямая логическая ссылка) в System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (Тип тип, корень XmlRootAttribute, String defaultNamespace) в System.Xml.Serialization.XmlSerializer..ctor (Тип тип, пространство String defaultName) в System.Xml.Serialization. XmlSerializer..ctor (Тип тип)

Почему должен существовать конструктор без параметров, чтобы обеспечить успешную сериализацию xml?

РЕДАКТИРОВАТЬ: спасибо за ответ cfeduke. Конструктор без параметров может быть закрытым или внутренним.

Морган Ченг
источник
1
Если вам интересно, я нашел, как создавать объекты, не нуждаясь в конструкторе (см. Обновление) - но это совсем не поможет XmlSerializer - он все еще требует этого. Может быть, полезно для пользовательского кода.
Марк Гравелл
1
XmlSerializerдля десериализации требуется конструктор по умолчанию без параметров.
Амит Кумар Гош

Ответы:

243

Во время десериализации объекта класс, ответственный за десериализацию объекта, создает экземпляр сериализованного класса, а затем переходит к заполнению сериализованных полей и свойств только после получения экземпляра для заполнения.

Вы можете сделать свой конструктор privateили, internalесли хотите, просто пока он без параметров.

cfeduke
источник
1
О, так, я могу сделать ctor без параметров частным или внутренним, и сериализация все еще работает. Спасибо за Ваш ответ.
Морган Ченг
2
Да, я делаю это часто, хотя я пришел к выводу, что публичные конструкторы без параметров хороши тем, что позволяют вам использовать «new ()» с обобщениями и новым синтаксисом инициализации. Для параметризованных конструкторов используйте статические фабричные методы или реализацию шаблона компоновщика.
cfeduke
14
Совет по доступности - хороший, но ваше объяснение не имеет смысла для сериализации. Объект должен быть создан только для десериализации. Я бы рискнул предположить, что код проверки типа встроен в конструктор XmlSerializer, потому что один экземпляр может использоваться обоими способами.
Томер Габель
7
@jwg Один из примеров - когда вы отправляете свой XML в какой-либо веб-сервис и не заинтересованы в получении этих объектов в вашем собственном компоненте.
Томер Габель
5
Имейте в виду, что даже если вы создаете конструктор без параметров privateили internalвсе ваши свойства, значения которых были сериализованы, должны иметь publicустановщики.
chrnola
75

Это ограничение XmlSerializer. Обратите внимание, что BinaryFormatterи DataContractSerializer не требуют этого - они могут создать неинициализированный объект из эфира и инициализировать его во время десериализации.

Поскольку вы используете xml, вы можете подумать об использовании DataContractSerializerи пометке вашего класса [DataContract]/ [DataMember], но обратите внимание, что это меняет схему (например, нет эквивалента [XmlAttribute]- все становится элементами).

Обновление: если вы действительно хотите знать, BinaryFormatterи т.д. используйте FormatterServices.GetUninitializedObject()для создания объекта, не вызывая конструктор. Вероятно, опасно; Я не рекомендую использовать его слишком часто ;-p См. Также замечания по MSDN:

Поскольку новый экземпляр объекта инициализируется нулем, и конструкторы не запускаются, объект может не представлять состояние, которое этот объект считает действительным. Текущий метод должен использоваться только для десериализации, когда пользователь намерен немедленно заполнить все поля. Он не создает неинициализированную строку, поскольку создание пустого экземпляра неизменяемого типа не имеет смысла.

У меня есть собственный движок сериализации, но я не собираюсь его использовать FormatterServices; Мне очень нравится знать, что конструктор ( любой конструктор) действительно выполняется.

Марк Гравелл
источник
Спасибо за совет о FormatterServices.GetUninitializedObject (Type). :)
Омер ван Клоэтен
6
Хех; оказывается, что я не следую своему собственному совету; Protobuf-net (опционально) разрешено FormatterServicesиспользовать целую вечность
Марк Гравелл
1
Но что я не понимаю, так это в том случае, если конструктор не указан, компилятор создает открытый конструктор без параметров. Так почему же этого недостаточно для механизма десериализации xml?
toddmo
Если я хочу десериализовать XML и инициализировать определенный объект, используя их конструктор (чтобы элементы / атрибуты предоставлялись через конструктор), есть ли ЛЮБОЙ способ добиться этого? Разве нет способа настроить процесс сериализации так, чтобы он строил объекты, используя их конструкторы?
Шимми Вейцхандлер
1
@ Шимми Нету; это не поддерживается. Там есть IXmlSerializable , но: что происходит после того, как конструктор, и б: это очень некрасиво и трудно получить правильную (особенно десериализацию) - я настоятельно рекомендую пытаться осуществить это, но: это не позволит вам использовать конструктор
Марк Гравелл
4

Ответ таков: без всякой веской причины.

Вопреки своему названию, XmlSerializerкласс используется не только для сериализации, но и для десериализации. Он выполняет определенные проверки вашего класса, чтобы убедиться, что он будет работать, и некоторые из этих проверок имеют отношение только к десериализации, но в любом случае он выполняет их все, потому что он не знает, что вы собираетесь делать позже.

Проверка того, что ваш класс не прошел, является одной из проверок, которые имеют отношение только к десериализации. Вот что происходит:

  • Во время десериализации XmlSerializerкласс должен будет создавать экземпляры вашего типа.

  • Чтобы создать экземпляр типа, необходимо вызвать конструктор этого типа.

  • Если вы не объявили конструктор, компилятор уже предоставил конструктор по умолчанию без параметров, но если вы объявили конструктор, то это единственный доступный конструктор.

  • Таким образом, если объявленный вами конструктор принимает параметры, то единственный способ создать экземпляр вашего класса - вызвать тот конструктор, который принимает параметры.

  • Однако XmlSerializerон не способен вызывать любой конструктор, кроме конструктора без параметров, поскольку он не знает, какие параметры передавать конструкторам, которые принимают параметры. Таким образом, он проверяет, есть ли в вашем классе конструктор без параметров, и, поскольку он этого не делает, происходит сбой.

Итак, если бы XmlSerializerкласс был написан таким образом, чтобы выполнять только проверки, относящиеся к сериализации, то ваш класс прошел бы, потому что в сериализации нет абсолютно ничего, что требовало бы иметь конструктор без параметров.

Как уже отмечали другие, быстрое решение вашей проблемы - просто добавить конструктор без параметров. К сожалению, это также грязное решение, потому что это означает, что вы не можете readonlyинициализировать какие-либо члены из параметров конструктора.

В дополнение ко всему этому, XmlSerializerкласс мог бы быть написан таким образом, чтобы позволить даже десериализацию классов без конструкторов без параметров. Все, что нужно, это использовать «Шаблон проектирования фабричных методов» (Википедия) . Судя по всему, Microsoft решила, что этот шаблон проектирования слишком сложен для программистов DotNet, которых, очевидно, не следует путать с такими вещами. Таким образом, по мнению Microsoft, программистам DotNet лучше придерживаться конструкторов без параметров.

Майк Накис
источник
Вы скажете: 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.если вы не знаете, какие параметры передать конструктору, то как он узнает, какие параметры передать фабрике? Или какую фабрику использовать? Я не могу представить, чтобы этот инструмент был более простым в использовании - вы хотите десериализовать класс, затем позволить десериализатору создать экземпляр по умолчанию, а затем заполнить каждое поле, которое вы пометили. Легко.
Чак
0

Прежде всего, это то, что написано в документации . Я думаю, что это одно из ваших полей класса, а не главное - и как вы хотите, чтобы десериализатор построил его обратно без построения без параметров?

Я думаю, что есть обходной путь, чтобы сделать конструктор приватным.

Дмитрий Халатов
источник