Чому для XML-серіалізаційного класу потрібен конструктор без параметрів


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, Булева директна референція, Boolean castOnError) на System.Xml.Serialization.ModelScope.GetTypeModel (Тип Булева пряма довідка) на System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (Тип типу, XmlRootAttribute root, String defaultNamespace) у System.Xml.Serialization.XmlSerializer..ctor (Тип типу, Простір String defaultName) на System.Xml.Serialization. XmlSerializer..ctor (тип типу)

Чому повинен бути конструктор без параметрів, щоб дозволити серіалізацію xml для успіху?

EDIT: дякую за відповідь cfeduke Конструктор без параметрів може бути приватним або внутрішнім.


1
Якщо вас цікавить, я знайшов, як створити об’єкти, не потребуючи конструктора (див. Оновлення) - але це зовсім не допоможе XmlSerializer - він все ще вимагає цього. Можливо, корисний для користувацького коду.
Марк Гравелл

1
XmlSerializerдля десеріалізації потрібен конструктор без параметрів за замовчуванням.
Аміт Кумар Гош

Відповіді:


243

Під час десеріалізації об'єкта клас, відповідальний за десеріалізацію об'єкта, створює екземпляр серіалізованого класу, а потім переходить до заповнення серіалізованих полів та властивостей лише після придбання екземпляра для заповнення.

Ви можете зробити свій конструктор privateабо, internalякщо хочете, лише до тих пір, поки він не параметр.


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
@Shimmy nope; це не підтримується. Там є IXmlSerializable , але: що відбувається після того, як конструктор, і б: це дуже некрасиво і важко встановити правильну (особливо десеріалізацію) - я настійно рекомендую намагатися здійснити це, але: це не дозволить вам використовувати конструктор
Марк Гравелл

4

Відповідь: без жодної вагомої причини.

Всупереч своїй назві, XmlSerializerклас використовується не тільки для серіалізації, але і для десеріалізації. Він виконує певні перевірки у вашому класі, щоб переконатися, що він буде працювати, а деякі з цих перевірок мають відношення лише до дезаріалізації, але він все-таки виконує їх, оскільки не знає, що ви збираєтесь робити згодом.

Перевірка, яку ваш клас не пройшов - це одна з перевірок, що стосуються лише дезаріалізації. Ось що відбувається:

  • Під час десеріалізації XmlSerializerкласу потрібно буде створити екземпляри вашого типу.

  • Щоб створити екземпляр типу, потрібно викликати конструктор цього типу.

  • Якщо ви не оголосили конструктора, компілятор вже надав конструктор без параметрів за замовчуванням, але якщо ви оголосили конструктор, то це єдиний доступний конструктор.

  • Отже, якщо заявлений вами конструктор приймає параметри, то єдиний спосіб інстанціювати свій клас - це викликати той конструктор, який приймає параметри.

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

Отже, якби XmlSerializerклас був написаний таким чином, щоб виконувати лише перевірки, що стосуються серіалізації, то ваш клас пройшов би, тому що про серіалізацію абсолютно нічого, що обумовлює необхідність створення конструктора без параметрів, не існує.

Як уже зазначали інші, швидке рішення вашої проблеми - просто додати конструктор без параметрів. На жаль, це також брудне рішення, оскільки це означає, що ви не можете мати жодних readonlyчленів, ініціалізованих із параметрів конструктора.

На додаток до всього цього, XmlSerializerклас міг бути написаний таким чином, щоб дозволити рівномірну деріаріалізацію класів без конструкторів без параметрів. Все, що знадобилося б, було б використовувати "Шаблон дизайну заводських методів" (Вікіпедія) . З огляду на це, Microsoft вирішила, що ця модель дизайну є надто передовою для програмістів DotNet, яких, мабуть, не слід непотрібно плутати з такими речами. Отже, програмістам DotNet слід краще дотримуватися конструкторів без параметрів, на думку Microsoft.


Ви кажете, 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. Якщо він не знає, які параметри передавати конструктору, то як би він знав, які параметри передавати на завод? Або яку фабрику використовувати? Я не можу уявити, що цей інструмент є більш простим у використанні - ви хочете клас деріаріалізувати, а потім дозволити deserializer зробити екземпляр за замовчуванням, а потім заповнити кожне поле, яке ви позначили. Легко.
Чак

0

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

Я думаю, що існує рішення, щоб зробити конструктор приватним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.