Сериализация — это автоматический процесс преобразования структур данных или состояний объектов в формат, который Unity может сохранять и реконструировать позже. (Дополнительную информацию см. в документации по сериализации сценариев.)
В некоторых случаях сериализация скрипта может вызвать ошибки. Исправления некоторых из них перечислены ниже.
Вызов Unity Scripting API из конструктора или инициализаторов полей
Вызов Scripting API, например GameObject.Find внутри MonoBehaviour конструктор или инициализатор поля вызывает ошибку: «Find нельзя вызывать из конструктора MonoBehaviour (или инициализатора поля экземпляра), вместо этого вызовите его в Awake или Start».
Исправьте это, выполнив вызов Scripting API в MonoBehaviour.Start, а не в конструкторе.
Вызов Unity Scripting API во время десериализации
Вызов Scripting API, такого как GameObject.Find, из конструктора класса, отмеченного System.Serializable
вызывает ошибку: «Find нельзя вызывать во время сериализации, вместо этого вызовите его из Awake или Start».
Чтобы исправить это, отредактируйте свой код, чтобы он не вызывал вызовы Scripting API в любых конструкторах для любых сериализованных объектов.
Потокобезопасный API скриптов Unity
На большую часть Scripting API распространяются перечисленные выше ограничения. Исключением являются только некоторые части API сценариев Unity, которые можно вызывать из любого места. Это:
-
Debug.Log
-
Mathf функции
-
Простые автономные структуры; например, математические структуры, такие как Vector3 и QuaternionСтандартный способ Unity для представления поворотов в виде данных. При написании кода, имеющего дело с поворотами, обычно следует использовать класс Quaternion и его методы. Подробнее
См. в Словарь
Чтобы снизить риск ошибок во время сериализации, вызывайте только автономные методы API, которым не нужно получать или устанавливать данные в самом Unity. Вызывайте их только в том случае, если нет альтернативы.
Gwalf
#1
Оставлено
:
3 апреля 2018 г. 15:25:38(UTC)
Статус: Активный участник
Группы: Участники
Зарегистрирован: 17.01.2018(UTC)
Сообщений: 38
Сказал(а) «Спасибо»: 1 раз
Нет доступа к ЦР.
Ping-CA выдал ошибку сериализации: CA error.jpg (75kb) загружен 49 раз(а).
С чем связана данная ошибка?
Как восстановить работу УЦ?
Захар Тихонов
#2
Оставлено
:
3 апреля 2018 г. 15:29:48(UTC)
Статус: Сотрудник
Группы: Участники
Зарегистрирован: 17.08.2015(UTC)
Сообщений: 3,063
Откуда: Калининград
Сказал «Спасибо»: 35 раз
Поблагодарили: 548 раз в 525 постах
Выполните команду на ЦР
Get-CAReference
и приложите вывод.
Также пришлите вывод команды с сервера ЦС
(Get-Item ‘CA:<имя ЦС>’).Subject
Gwalf
#3
Оставлено
:
3 апреля 2018 г. 15:33:08(UTC)
Статус: Активный участник
Группы: Участники
Зарегистрирован: 17.01.2018(UTC)
Сообщений: 38
Сказал(а) «Спасибо»: 1 раз
ЦР и ЦС развернуты в единственном экземпляре на одной машине.
Сертификаты не просрочены.
Захар Тихонов
#4
Оставлено
:
3 апреля 2018 г. 15:42:41(UTC)
Статус: Сотрудник
Группы: Участники
Зарегистрирован: 17.08.2015(UTC)
Сообщений: 3,063
Откуда: Калининград
Сказал «Спасибо»: 35 раз
Поблагодарили: 548 раз в 525 постах
Ок, еще раз:
Выполните команду на ЦР
Get-CAReference
и приложите вывод.
Также пришлите вывод команды с сервера ЦС
(Get-Item ‘CA:<имя ЦС>’).Subject
Если это один сервер, то это не отменяет того, что нужно выполнить команды и прислать выводы.
Gwalf
#5
Оставлено
:
3 апреля 2018 г. 16:06:22(UTC)
Статус: Активный участник
Группы: Участники
Зарегистрирован: 17.01.2018(UTC)
Сообщений: 38
Сказал(а) «Спасибо»: 1 раз
Get-CAReference: Get-CAReference.jpg (122kb) загружен 40 раз(а).
Где можно проверить имя ЦС?
Отредактировано пользователем 3 апреля 2018 г. 16:17:07(UTC)
| Причина: Не указана
Захар Тихонов
#6
Оставлено
:
3 апреля 2018 г. 16:18:09(UTC)
Статус: Сотрудник
Группы: Участники
Зарегистрирован: 17.08.2015(UTC)
Сообщений: 3,063
Откуда: Калининград
Сказал «Спасибо»: 35 раз
Поблагодарили: 548 раз в 525 постах
Посмотрите его в Диспетчере УЦ.
Gwalf
#7
Оставлено
:
3 апреля 2018 г. 16:39:44(UTC)
Статус: Активный участник
Группы: Участники
Зарегистрирован: 17.01.2018(UTC)
Сообщений: 38
Сказал(а) «Спасибо»: 1 раз
Subject.jpg (30kb) загружен 34 раз(а).
Захар Тихонов
#8
Оставлено
:
3 апреля 2018 г. 16:43:10(UTC)
Статус: Сотрудник
Группы: Участники
Зарегистрирован: 17.08.2015(UTC)
Сообщений: 3,063
Откуда: Калининград
Сказал «Спасибо»: 35 раз
Поблагодарили: 548 раз в 525 постах
Возможно остановлена служба SQL. В Диспетчере конфигурации SQL, ваш экземпляр SQL запущен?
Gwalf
#9
Оставлено
:
3 апреля 2018 г. 17:00:12(UTC)
Статус: Активный участник
Группы: Участники
Зарегистрирован: 17.01.2018(UTC)
Сообщений: 38
Сказал(а) «Спасибо»: 1 раз
Служба SQL запущена.
В логах по-кругу идут 4 ошибки:
1.
Службе регистрации Крипто-Про УЦ 2.0 не удалось обновить CDP с УЦ ПЭС.(Код ошибки 768)
2.
При синхронизации количества пользователей с ЦС УЦ ПЭС получено исключение.
System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]: Закрытый ключ сертификата, которым подписан запрос, недействителен. (Fault Detail is equal to An ExceptionDetail, likely created by IncludeExceptionDetailInFaults=true, whose value is:
System.ArgumentException: Закрытый ключ сертификата, которым подписан запрос, недействителен.
at CertificateService.BusinessLogic.CertScriptContext.VerifyCertificate(X509Certificate2 certificate)
at CertificateService.ServiceImplementation.CertServiceAuthorizationManager.CheckAccess(OperationContext operationContext, Message& message)
at System.ServiceModel.Dispatcher.AuthorizationBehavior.Authorize(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)).
3.
Ошибка при обращении к ЦС. System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. —> System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]: Закрытый ключ сертификата, которым подписан запрос, недействителен.
Server stack trace:
at System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter)
at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at CertificateService.ServiceContracts.ICertRequestContract.GetPolicy(String authority)
— End of inner exception stack trace —
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at RegistrationService.RegSrv.TransmitQueueService.OnMessage(IScriptContext`1 context, Message message)
HResult = -2146232828 (0x80131604)
=== InnerException #2 ===
System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]: Закрытый ключ сертификата, которым подписан запрос, недействителен. (Fault Detail is equal to An ExceptionDetail, likely created by IncludeExceptionDetailInFaults=true, whose value is:
System.ArgumentException: Закрытый ключ сертификата, которым подписан запрос, недействителен.
at CertificateService.BusinessLogic.CertScriptContext.VerifyCertificate(X509Certificate2 certificate)
at CertificateService.ServiceImplementation.CertServiceAuthorizationManager.CheckAccess(OperationContext operationContext, Message& message)
at System.ServiceModel.Dispatcher.AuthorizationBehavior.Authorize(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)).
HResult = -2146233087 (0x80131501).
4.
При запросе политики с ЦС получено исключение.
System.Runtime.Serialization.SerializationException: Во время сериализации произошла ошибка. —> System.Runtime.Serialization.SerializationException: Type ‘System.ServiceModel.ExceptionDetail’ in Assembly ‘System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’ is not marked as serializable.
at System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type)
at System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo()
at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
at RegistrationService.BusinessLogic.MethodCall.SetThrownException(Exception thrownException)
at RegistrationService.RegSrv.TransmitQueueService.OnMessage(IScriptContext`1 context, Message message)
— End of inner exception stack trace —
at RegistrationService.BusinessLogic.ProcessClient.Wait(Queue queue, Conversation conversation, TimeSpan timeout)
at RegistrationService.BusinessLogic.ProcessClient.WaitComplete(TimeSpan timeout)
at RegistrationService.RegSrv.PolicyUpdateTask.Execute(IScriptContext`1 context)
Захар Тихонов
#10
Оставлено
:
3 апреля 2018 г. 17:02:45(UTC)
Статус: Сотрудник
Группы: Участники
Зарегистрирован: 17.08.2015(UTC)
Сообщений: 3,063
Откуда: Калининград
Сказал «Спасибо»: 35 раз
Поблагодарили: 548 раз в 525 постах
при выполнении команды Get-CAReference указан клиентский сертификат ЦР. Там указаны сроки действия сертификата. Откройте этот сертификат, найдите расширение срок действия ЗК. Если он истек, то выполните смену клиентского сертификата ЦР.
Пользователи, просматривающие эту тему
Guest
Быстрый переход
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.
Содержание
- Механизмы сериализации
- Сериализатор контрактов данных
- Сериализация производных типов
- Объектные ссылки
- Совместимость версий
- Упорядочение членов
- Пустые значения и null
- Сериализация коллекций
- Хуки сериализации
- Поддержка механизмов двоичной сериализации
- Двоичный сериализатор
- Атрибут [Serializable]
- Выполнение двоичной сериализации
- Атрибут [NonSerialized]
- Атрибут [OptionalField]
- Интерфейс ISerializable
- XML сериализатор
- Выполнение XML сериализации
- Обработка производных классов и объектных ссылок
- Сериализация коллекций
- IXmlSerializable
Сериализация — процесс преобразования объекта или группы связанных объектов в поток байт или набор XML узлов для сохранения (в базу данных, файл и т.д.) или передачи (например, по сети). Десериализация — обратный процесс — восстановление объекта из потока байт или XML узлов. Сериализация обычно используется для передачи объектов по сети или за границы приложения, для сохранения объектов внутри файлов или базы данных, а также для глубокого копирования объектов.
Классы для сериализации располагаются в двух пространствах имен: System.Runtime.Serialization
и System.Xml.Serialization
.
Механизмы сериализации
Для сериализации в .NET доступно 4 механизма:
- сериализатор контрактов данных
- двоичный сериализатор
- XML сериализатор (
XmlSerializer
) - интерфейс
IXmlSerializable
Первые три являются готовыми механизмами для сериализации и выполняют всю работу самостоятельно, интерфейс IXmlSerializable
предполагает реализацию механизма сериализации самостоятельно.
Наличие трех разных механизмов сериализации является исторически сложившимся.
Сериализатор контрактов данных является самым новым и универсальным. Он может быть использован при обмене информацией через протоколы обмена сообщениями. Он также обеспечивает хорошую переносимость версий и дает возможность десериализовать данные полученные из более ранней или более поздней версии типа, за счет того что устраняет связь между низкоуровневыми деталями объектов и структурой сериализованных данных. Сериализатор контрактов данных может быть использован в большинстве задач вместо двоичного и XML сериализатора.
Двоичный сериализатор прост в применении, хорошо автоматизирован и поддерживается повсеместно в .NET. Двоичная сериализация используется инфраструктурой Remoting, в т.ч. при взаимодействии между двумя доменами приложений в одном и том же процессе. Двоичный сериализатор работает быстрее чем сериализатор контрактов данных, но так дает худшую переносимость, поскольку тесно связывает внутреннюю структуру типа с форматом сериализованных данных. Он также не может быть использован для получения XML.
XmlSerializer
может генерировать только XML, и по сравнению с другими механизмами он менее функционален при сериализации сложных групп объектов. Однако во взаимодействии с XML он дает наибольшую функциональность, а также обеспечивает хорошую переносимость версий.
Реализация интерфейса IXmlSerializable
предполагает самостоятельное выполнение сериализации с помощью XmlReader
и XmlWriter
.
Вывод сериализатора контрактов данных и двоичного сериализатора оформляется с помощью подключаемого форматера. Форматер приводит форму финального представления в соответствие с конкретной средой или контекстом сериализации. Доступно два форматера: форматер XML и двоичный форматер. XML форматер используется в контексет чтения/записи XML, тектовых файлов и потоков, обмена сообщениями SOAP. Двоичный форматер используется в контексте произвольного потока байт. Двоичный вывод по размерам обычно меньше чем XML, иногда значительно. Теоретически механизм сериалзиции не связан с форматером, но на практике сериализатор контрактов данных использует XML форматер, а двоичный сериализатор — двоичный форматер.
Сериализатор контрактов данных
Использование сериализатора контрактов данных предполагает следующие три шага
- выбрать класс для использования:
DataContractSerializer
илиNetDataContractSerializer
- добавить сериализуемым типам и членам атрибуты
[DataContract]
и[DataMember]
(соответственно) - создать экземпляр сериализатора и вызвать его метод
WriteObject
илиReadObject
Существует два сериализатора контрактов данных:
DataContractSerializer
— обеспечивает слабую привязку типов .NET к типам контрактов данных. Может генерировать совместимый со стандартами XML код. Требует предварительной явной регистрации сериализуемых производных типов, чтобы иметь возможность сопоставлять имена контрактов данных с именами типов .NETNetDataContractSerializer
— характеризуется тесной привязкой типов .NET к типам контрактов данных, не требует явной регистрации сериализуемых производных типов, т.к. самостоятельно записывает полные имена типов и сборок сериализуемых типов
После выбора сериализатора необходимо всем сериализуемым типам добавить атрибут[DataContract]
, а их членам, которые необходимо включить в сериализацию, — атрибуты [DataMember]
:
namespace SerialTest { [DataContract] public class Person { [DataMember] public string Name; [DataMember] public int Age; } } |
После этого можно явно сериализовать и десериализовать объекты, создавая экземпляр DataContractSerializer
или NetDataContractSerializer
и вызывая метод WriteObject
или ReadObject
:
Person p = new Person { Name = «Stacey», Age = 30 }; var ds = new DataContractSerializer (typeof (Person)); using (Stream s = File.Create («person.xml»)) ds.WriteObject (s, p); // Сериализация Person p2; using (Stream s = File.OpenRead («person.xml»)) p2 = (Person) ds.ReadObject (s); // Десериализация Console.WriteLine (p2.Name + » « + p2.Age); // Stacey 30 |
Конструктору DataContractSerializer
необходимо передать тип корневого объекта — сериализуемый объект, который в XML дереве будет корневым элементом. Конструктор NetDataContractSerializer
этого не требует. В остальном их применение аналогично.
Оба типа сериализаторов по умолчанию принимаю форматер XML, поэтому с ними можно использовать XmlWriter
:
Person p = new Person { Name = «Stacey», Age = 30 }; var ds = new DataContractSerializer (typeof (Person)); // Добавить в вывод отступы: XmlWriterSettings settings = new XmlWriterSettings() { Indent = true }; using (XmlWriter w = XmlWriter.Create («person.xml», settings)) ds.WriteObject (w, p); System.Diagnostics.Process.Start («person.xml»); |
Имена XML элементов в выводе соответствуют именам контрактов данных, которые по умолчанию совпадают с именами типов в .NET, однако это можно переопределить с помощью атрибутов, задав альтернативное имя для элемента:
[DataContract (Name=«Candidate»)] public class Person { ... } |
Также можно изменить пространство имен по умолчанию для корневого элемента:
[DataContract (Namespace=«http://somedomain.com/contacts»)] public class Person { ... } |
Можно также переопределить имена членов данных:
[DataContract (Name=«Candidate», Namespace=«http://oreilly.com/nutshell»)] public class Person { [DataMember (Name=«FirstName»)] public string Name; [DataMember (Name=«ClaimedAge»)] public int Age; } |
Атрибут [DataMember]
может быть применен к частным и публичным полям и свойствам следующих типов данных:
- любой примитивный тип
DateTime
,TimeSpan
,Guid
,Uri
илиEnum
- типы, допускающие значение
null
вышеуказанных типов byte[]
(сериализуется в XML с применением кодировки Base64- любой тип с атрибутом
[DataContract]
- любой тип
IEnumerable
- любой тип с атрибутом
[Serializable]
или реализующий интерфейсISerializable
- любой тип реализующий интерфейс
IXmlSerializable
Можно использовать двоичный форматер:
Person p = new Person { Name = «Stacey», Age = 30 }; var ds = new DataContractSerializer (typeof (Person)); var s = new MemoryStream(); using (XmlDictionaryWriter w = XmlDictionaryWriter.CreateBinaryWriter (s)) ds.WriteObject (w, p); var s2 = new MemoryStream (s.ToArray()); Person p2; using (XmlDictionaryReader r = XmlDictionaryReader.CreateBinaryReader (s2, XmlDictionaryReaderQuotas.Max)) p2 = (Person) ds.ReadObject (r); |
Сериализация производных типов
При сериализации производных типов если используется NetDataContractSerializer
никаких дополнительных действий не требуется, необходимо только снабдить производный класс атрибутом[DataContract]
. Сериализатор будет записывать полностью определенные имена типов:
<Person ... z:Type=«SerialTest.Person» z:Assembly=«SerialTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null»> |
В случае с DataContractSerializer
сериализатору необходимо сообщить обо всех производных типах, которые он может сериализовать. Если этого не сделать при десерриализации возникнет ошибка, т.к. сериализатор не сможет узнать в какой именно тип следует преобразовать элемент:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// Базовый класс: [DataContract] public class Person { [DataMember] public string Name; [DataMember] public int Age; } // Производные классы: [DataContract] public class Student : Person { } [DataContract] public class Teacher : Person { } // Метод выполняющий глубокое клонирование с применением сериализации: static Person DeepClone (Person p) { var ds = new DataContractSerializer (typeof (Person)); MemoryStream stream = new MemoryStream(); ds.WriteObject (stream, p); stream.Position = 0; return (Person) ds.ReadObject (stream); } // Применение метода: Person person = new Person { Name = «Stacey», Age = 30 }; Student student = new Student { Name = «Stacey», Age = 30 }; Teacher teacher = new Teacher { Name = «Stacey», Age = 30 }; Person p2 = DeepClone (person); // OK Student s2 = (Student) DeepClone (student); // SerializationException Teacher t2 = (Teacher) DeepClone (teacher); // SerializationException |
Сообщить сериализатору о производных типах можно при создании экземпляра DataContractSerializer
, либо в самом типе с помощью атрибута[KnownType]
:
var ds = new DataContractSerializer (typeof (Person), new Type[] { typeof (Student), typeof (Teacher) } ); [DataContract, KnownType (typeof (Student)), KnownType (typeof (Teacher))] public class Person |
Сериализованный объект Student
будет выглядеть так:
<Person xmlns=«…» xmlns:i=«http://www.w3.org/2001/XMLSchema-instance» i:type=«Student» > … <Person> |
Поскольку в качестве корневого типа указан Person
, корневой элемент имеет его имя, а действительный подкласс описан отдельно в атрибуте type
.
Объектные ссылки
Ссылки на другие объекты также сериализуются. Напрмер:
[DataContract] public class Person { [DataMember] public string Name; [DataMember] public int Age; [DataMember] public Address HomeAddress; } [DataContract] public class Address { [DataMember] public string Street, Postcode; } |
Будет сериализовано в:
<Person…> <Age>…</Age> <HomeAddress> <Street>…</Street> <Postcode>…</Postcode> </HomeAddress> <Name>…</Name> </Person> |
Класс NetDataContractSerializer
всегда сохраняет эквивалентность ссылок на объекты. Класс DataContractSerializer
по умолчанию этого не делает. Это означает, что если на один и тот же объект имеются ссылки в двух разных местах, DataContractSerializer
запишет его дважды. Например, для такого объекта:
[DataContract] public class Person { [DataMember] public Address HomeAddress, WorkAddress; } Person p = new Person { Name = «Stacey», Age = 30 }; p.HomeAddress = new Address { Street = «Odo St», Postcode = «6020» }; p.WorkAddress = p.HomeAddress; |
XML будет содержать тот же самый адрес дважды:
... <HomeAddress> <Postcode>6020</Postcode> <Street>Odo St</Street> </HomeAddress> … <WorkAddress> <Postcode>6020</Postcode> <Street>Odo St</Street> </WorkAddress> |
При последующей десериализации WorkAddress
и HomeAddress
будут преобразованы в два разных объекта. Преимуществом такого подходя является простота и совместимость XML, а недостатком — большой размер XML, потеря ссылочной целостности и невозможность справиться с циклическими ссылками.
Чтобы сохранить ссылочную эквивалентность, нужно в конструктор DataContractSerializer
для аргумента preserveObjectReferen
передать true
:
var ds = new DataContractSerializer (typeof (Person), null, 1000, false, true, null); |
При этом третьим аргументом необходимо передать максимальное количество объектных ссылок, которые сериализатор должен отслеживать (при привышении этого количество будет сгенерировано исключение). XML при сохранении ссылочной эквивалентности будет выглядеть так:
<Person xmlns=«http://schemas.datacontract.org/2004/07/SerialTest» xmlns:i=«http://www.w3.org/2001/XMLSchema-instance» xmlns:z=«http://schemas.microsoft.com/2003/10/Serialization/» z:Id=«1»> <Age>30</Age> <HomeAddress z:Id=«2»> <Postcode z:Id=«3»>6020</Postcode> <Street z:Id=«4»>Odo St</Street> </HomeAddress> <Name z:Id=«5»>Stacey</Name> <WorkAddress z:Ref=«2» i:nil=«true» /> </Person> |
Совместимость версий
Можно добавлять и удалять члены в сериализуемые типы, не нарушая при этом прямой и обратной совместимости. Сериализаторы всегда пропускают при сериализации члены, для которых не установлен атрибут [DataMember]
, а при десериализации не генерируют исключений, если для члена, снабженного атрибутом [DataMember]
, нет сериализованных данных. Нераспознанные данные, присутствующие в потоке сериализации, но отсутствующие у десериализуемого типа, также молча пропускаются. Однако если тип реализует интерфейс IExtensibleDataObject
, нераспознанные данные пропускаться не будут, а будут сохраняться в объект ExtensionDataObject
, который может быть получен с помощью свойства ExtensionData
:
[DataContract] public class Person : IExtensibleDataObject { [DataMember] public string Name; [DataMember] public int Age; ExtensionDataObject IExtensibleDataObject.ExtensionData { get; set; } } |
Если член является важным для объекта можно потребовать его присутствия с помощью IsRequired
:
[DataMember (IsRequired=true)] public int ID; |
Если такой член отсутствует, при десериализации будет сгенерировано исключение.
Упорядочение членов
Порядок следования членов при сериализации и десериализации играет определенное значение: при десериализации неправильно упорядоченные члены будут пропущены.
Упорядочение осуществляется при сериализации по следующим правилам:
- сначала сериализуются члены базового класса, потом производного
- для членов с установленным аргументом
Order
учитывается его значение (от меньшего к большему) - в последнюю очередь учитывается алфавитный порядок имен членов
Таким образом поле Age
будет предшествовать полю Name
, если только не поменять их порядок с помощью аргумента Order
:
[DataContract] public class Person { [DataMember (Order=0)] public string Name; [DataMember (Order=1)] public int Age; } |
Пустые значения и null
Члены типа, значениями которых является null
или пустые значения, при сериализации по умолчанию все равно записываются с пустым значением:
<Person xmlns=«…» xmlns:i=«http://www.w3.org/2001/XMLSchema-instance»> <Name i:nil=«true» /> </Person> |
Однако это не всегда целесообразно, поскольку ведет к бесполезному расходу пространства особенно для типов с множеством пустых полей и свойств. Поэтому сериализатору можно сообщить о необходимости пропускать пустые поля и свойства. Сделать это можно с помощью аргумента EmitDefaultValue
:
[DataContract] public class Person { [DataMember (EmitDefaultValue=false)] public string Name; [DataMember (EmitDefaultValue=false)] public int Age; } |
В результате Name
будет пропущен, если его значение равно null
, а Age
будет пропущен, если его значение равно 0
.
Сериализация коллекций
Сериализаторы контрактов данных могут сериализовать и десериализовать любую коллекцию:
[DataContract] public class Person { ... [DataMember] public List<Address> Addresses; } [DataContract] public class Address { [DataMember] public string Street, Postcode; } |
Вывод:
<Person ...> … <Addresses> <Address> <Postcode>6020</Postcode> <Street>Odo St</Street> </Address> <Address> <Postcode>6152</Postcode> <Street>Comer St</Street> </Address> </Addresses> … </Person> |
При этом сериализатор не кодирует информацию о конкретном типе коллекции. Это позволяет при десериализации изменить тип коллекции без генерации ошибок.
При сериализации кастомного типа коллекции можно настроить имя XML узла для каждого элемента коллекции. Для этого используется атрибут[CollectionDataContract]
:
[CollectionDataContract (ItemName=«Residence»)] public class AddressList : Collection<Address> { } [DataContract] public class Person { ... [DataMember] public AddressList Addresses; } |
Вывод будет таким:
... <Addresses> <Residence> <Postcode>6020</Postcode <Street>Odo St</Street> </Residence> ... |
Атрибут [CollectionDataContract]
также позволяет задать аргументы Namespace
и Name
. Аргумент Name
не используется, когда коллекция сериализуется как свойство другого объекта, но применяется если коллекция сериализуется как корневой объект.
Атрибут [CollectionDataContract]
может также использоваться для управления сериализацией словарей:
[CollectionDataContract (ItemName=«Entry», KeyName=«Kind», ValueName=«Number»)] public class PhoneNumberList : Dictionary <string, string> { } [DataContract] public class Person { ... [DataMember] public PhoneNumberList PhoneNumbers; } |
Вывод:
... <PhoneNumbers> <Entry> <Kind>Home</Kind> <Number>08 1234 5678</Number> </Entry> <Entry> <Kind>Mobile</Kind> <Number>040 8765 4321</Number> </Entry> </PhoneNumbers> |
Хуки сериализации
Непосредственно до и после сериализации или десерализации можно выполнить специальный метод. Задать такой метод можно с помощью следующих атрибутов:
[OnSerializing]
— указывает метод для вызова перед сериализацией[OnSerialized]
— указывает метод для вызова после сериализации[OnDeserializing]
— указывает метод для вызова перед десериализацией[OnDeserialized]
— указывает метод для вызова после десериализации
Такой метод должен принимать единственный параметр, имеющий тип StreamingContext
, но необходим он только для согласованности с механизмом двоичной сериализации и не используется сериализаторами контрактов данных.
Поскольку сериализаторы не выполняют конструкторы и инициализаторы при десериализации, данные методы могут использоваться как конструкторы или инициализаторы полей при десериализации, чтобы присвоить значения членам, исключенным из сериализации, поскольку без этого исключенные из сериализации члены получат при десериализации дефолтные значения для своих типов, а не те значения, которые им присваиваются в конструкторах или инициализаторах.
Методы могут быть как публичными так и частными. Производные типы могут определять свои хуки и они также будт вызваны.
Поддержка механизмов двоичной сериализации
Сериализаторы контрактов данных также может сериализовать типы помеченные атрибутом [Serializable]
и реализующие интерфейс ISerializable
. При этом не происходит переключения на двоичную сериализацию, но многие механизмы работают ка при двоичной сериализации: учитываются атрибуты [NonSerialized]
, выполняются методы GetObjectData
и конструкторы десериализации.
Не допускается одновременно использовать атрибуты контрактов данных и атрибуты двоичной сериализации.
Двоичный сериализатор
Сделать тип поддерживающим двоичную сериализацию можно двумя путями:
- добавить типу атрибут
[Serializable]
- реализовать в типе интерфейс
ISerializable
Добавление атрибута проще, но реализация интерфейса дает больше возможностей.
Атрибут [Serializable]
Сделать тип сериализуемым можно с помощью единственного атрибута:
[Serializable] public sealed class Person { public string Name; public int Age; } |
Атрибут [Serializable]
указывает сериализатору на необходимость включать все поля данного типа, как публичные так и частные, но не включать свойства.
Каждое поле должно иметь сериализуемый тип, т.е. тип также помеченный атрибутом [Serializable]
, либо реализующий интерфейс ISerializable
. Примитивные типы и множество других типов .NET являются сериализуемыми.
Атрибут Serializable
не наследуется, поэтому производные классы не являются автоматически сериализуемыми, если их не пометить специально атрибутом Serializable
.
Свойства типа не сериализуются, но сериализуются лежащие в основе их автоматически создаваемые поля (к сожалению эти поля могут переименовываться при перекомпиляции).
Выполнение двоичной сериализации
Чтобы выполнить двоичную сериализацию необходимо создать объект форматера и вызвать его метод Serialize
. Для двоичной сериализации предусмотрено два форматера:
BinaryFormatter
— более эффективный, генерирует небольшой вывод за меньшее время. Определен в пространстве именSystem.Runtime.Serialization.Formatters.Binary
SoapFormatter
— поддерживает базовый обмен сообщениями, менее функционален, не поддерживает сериализацию обобщенных типов и фильтрацию посторонних данных. Определен в пространстве именSystem.Runtime.Serialization.Formatters.Soap
Оба форматера используются одинаково:
Person p = new Person() { Name = «George», Age = 25 }; IFormatter formatter = new BinaryFormatter(); using (FileStream s = File.Create («serialized.bin»)) formatter.Serialize (s, p); |
Метод Deserialize
восстанавливает объект:
using (FileStream s = File.OpenRead («serialized.bin»)) { Person p2 = (Person) formatter.Deserialize (s); Console.WriteLine (p2.Name + » « + p.Age); // George 25 } |
При воссоздании объектов десериализатор пропускает все конструкторы.
Сериализованные данные включают полные сведения о типе и сборке, поэтому если при десереиализации попытаться привести тип к совместимому типу, возникнет ошибка. Десериализатор полностью восстанавливает объектные ссылки в их исходное состояние.
Двоичные сериализаторы поддерживают также атрибуты [OnSerializing]
,[OnSerialized]
, [OnDeserializing]
и [OnDeserialized]
. Их применение не отличается от сериализаторов контрактов данных.
Атрибут [NonSerialized]
В отличие от сериализаторов контрактов данных, которые требую помечать атрибутами все сериализуемые поля, двоичный сериализатор этого не требует и по умолчанию включает все поля сериализуемого объекта. Исключить отдельные поля из сериализации можно отметив их атрибутом [NonSerialized]
:
[Serializable] public sealed class Person { public string Name; public DateTime DateOfBirth; [NonSerialized] public int Age; } |
Несериализованные члены при десериализации всегда получают пустое значение или null, даже если инициализаторы полей и конструкторы устанавливают их по другому.
Атрибут [OptionalField]
По умолчанию добавление нового поля нарушает совместимость с уже сериализованными данными и сериализатор выбросит исключение. Этого можно избежать добавив к новому полю атрибут [OptionalField]
:
[Serializable] public sealed class Person { public string Name; [OptionalField (VersionAdded = 2)] public DateTime DateOfBirth; } |
Это указывает сериализатору не генерировать исключение, если в потоке сериализованных данных он не встреит помеченного поля, а считать поле просто не сериализованным и оставить его пустым (ему можно затем присвоить значение с помощью метода [OnDeserializing]
).
Аргумент VersionAdded
— это целочисленное значение, инкрементируемое при каждом добавлении к типу новых полей. Оно носит скорее справочный характер и никак не влияет на сериализацию.
Вообще в целях сохранения версий рекомендуется избегать переименования, удаления и изменения типа полей.
Когда BinaryFormatter
при десериализации обнаруживает в потоке сериализованных данных поле, которое не объявлено в типе, он его просто отбросит, аSoapFormatter
сгененриует исключение.
Интерфейс ISerializable
Реализация интерфейса ISerializable
предоставляет типу полный контроль над тем, как производится его двоичная сериализая и десериализация. Определение интерфейса выглядит следующим образом:
public interface ISerializable { void GetObjectData (SerializationInfo info, StreamingContext context); } |
Метод GetObjectData
запускается при сериализации и наполняет объект SerializationInfo
(словарь пар имя-значение) данными из всех полей, подлежащих сериализации. Пример реализации для типа с двумя сериализуемыми полями Name
и DateOfBirth
:
public virtual void GetObjectData (SerializationInfo info, StreamingContext context) { info.AddValue («Name», Name); info.AddValue («DateOfBirth», DateOfBirth); } |
В примере ключи значений объекта SerializationInfo
совпадают с именами полей в типе, но это не является обязательным: допустимо использовать любые строковые ключи, при условии, что конструктор десериализации сможет их преобразовать в имена полей. Сами значения могут относится к любому сериализуемому типу: при необходимости будет выполнена рекурсивная сериализация. Также допустимо если значением в словаре будет null
.
Рекомендуется объявлять метод GetObjectData
как virtual
, чтобы производные классы могли расширять сериализацию не реализуя заново интрефейсISerializable
.
Класс SerializationInfo
также содержит свойства позволяющие управлять типом и сборкой, в которые должен быть помещен десериализованный экземпляр.
Параметр StreamingContext
— это структрура, по мимо прочего содержащая значения enum, указывающие откуда поступает сериализованный экземпляр.
Помимо реализации интерфейса ISerializable
, тип, управляющий собственной сериализацией должен содержать конструктор десериализации, принимающий те же два параметра, что и метод GetObjectData
. Он может быть объявлен с любым модификатором доступа.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[Serializable] public class Team : ISerializable { public string Name; public List<Person> Players; public virtual void GetObjectData (SerializationInfo si, StreamingContext sc) { si.AddValue («Name», Name); si.AddValue («PlayerData», Players.ToArray()); } public Team() {} // Конструктор десериализации protected Team (SerializationInfo si, StreamingContext sc) { Name = si.GetString («Name»); Person[] a = (Person[]) si.GetValue («PlayerData», typeof (Person[])); Players = new List<Person> (a); } } |
Для ряда типов класс SerializationInfo
имеет специальные методы Get*
, например, GetString
, для более удобного получения значений по ключам. Если объект SerializationInfo
не имеет значения с запрошенным ключом, будет сгенерировано исключение.
XML сериализатор
В пространстве имен System.Xml.Serialization
определен еще один сериализатор — XmlSerializer
. Он позволяет сериализовать типы в XML файлы. Как и при двоичной сериализации доступно два подхода:
- добавлять к типу атрибуты из пространства имен
System.Xml.Serialization
- реализовывать интерфейс
IXmlSerializable
В отличае от двоичной сериализации, реализация интерфейса IXmlSerializable
полностью исключает применение встроенного сериализатора, оставляя разработчику самостоятельное написание кода сериализации с использованием XmlReader
и XmlWriter
.
Выполнение XML сериализации
Для использования XmlSerializer
необходимо создать его экземпляр и вызвать на нем Serialize
или Deserialize
, передав им в качестве аргументов поток (Stream
), в который будут записываться сериализуемые данные (или из которого они будут читаться) и сериализуемый (или десериализуемый) объект:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// Сериализуемый тип: public class Person { public string Name; public int Age; } // Сериализуемый объект: Person p = new Person(); p.Name = «Stacey»; p.Age = 30; // Сериализация: XmlSerializer xs = new XmlSerializer (typeof (Person)); using (Stream s = File.Create («person.xml»)) xs.Serialize (s, p); // Десериализация: Person p2; using (Stream s = File.OpenRead («person.xml»)) p2 = (Person) xs.Deserialize (s); Console.WriteLine (p2.Name + » « + p2.Age); // Stacey 30 |
Методы Serialize
и Deserialize
могут работать с Stream
,XmlWriter
/XmlReader
или TextWriter
/TextReader
.
Для сериализации с помощью XmlSerializer
добавлять сериализуемому типу какие-либо атрибуты не требуется. По умолчанию сериализуются все открытые поля и свойства типа. Исключить члены из сериализации можно с помощью атрибута [XmlIgnore]
:
public class Person { ... [XmlIgnore] public DateTime DateOfBirth; } |
XmlSerializer
не распознает атрибут [OnDeserializing]
, а вместо него при десериализации использует конструктор без парарметров (в том числе неявно заданный) и генерирует исключение если он не найден. Также при десериализации выполняются инициализаторы полей:
public class Person { public bool Valid = true; // Будет выполнено при десриализации } |
Следующие типы обрабатываются специальным образом:
- примитивные типы,
DateTime
,TimeSpan
,Guid
и их версии, допускающие значение null вставляются как значения byte[]
— преобразуется в base 64XmlAttribute
иXmlElement
— их контент непосредственно вставляется в XML (без обработки)- любой тип, реализующий
IXmlSerializable
— обрабатывается в соответствии с собственной реализацией - все типы коллекций
XmlSerializer
обладает хорошей совместимостью: при десериализации он не жалуется если элементы или атрибуты отсутствуют либо встречаются лишние данные.
По умолчанию члены сериализуются в XML элементы. Если к члену добавить атрибут [XmlAttribute]
, он будет сериализован в XML атрибут:
[XmlAttribute] public int Age; |
С помощью атрибутов можно изменять стандартные имена элементов и атрибутов:
public class Person { [XmlElement («FirstName»)] public string Name; [XmlAttribute («RoughAge»)] public int Age; } |
Будет сериализовано:
<Person RoughAge=«30» ...> <FirstName>Stacey</FirstName> </Person> |
Стандартное пространство имен является пустым, задать его можно с помощью аргумента Namespace
атрибутов [XmlElement]
и [XmlAttribute]
. Можно также назначить имя и пространство имен самому типу с помощью атрибута [XmlRoot]
:
[XmlRoot («Candidate», Namespace = «http://mynamespace/test/»)] public class Person { ... } |
XmlSerializer
записывает элементы в порядке, в котором они определены в классе. Изменить этот порядок можно с помощью аргумента Order
элемента XmlElement
:
public class Person { [XmlElement (Order = 2)] public string Name; [XmlElement (Order = 1)] public int Age; } |
Если аргумент Order
используется, он должен быть проставлен для всех элементов.
Обработка производных классов и объектных ссылок
Для того чтобы XmlSerializer
мог корректно сериализовать и десериализовать производные классы, ему необходимо сообщить об их существовании. Сделать это можно двумя способами:
- с помощью атрибута
[XmlInclude]
, применяемого к базовому сериализуемому типу:[XmlInclude (typeof (Student))]
[XmlInclude (typeof (Teacher))]
public class Person { public string Name; }
- указать производные типы при создании экземпляра
XmlSerializer
:XmlSerializer xs = new XmlSerializer (typeof (Person),
new Type[] { typeof (Student), typeof (Teacher) } );
В обоих случаях сериализатор запишет производный тип в атрибут type
:
<Person xmlns:xsi=«http://www.w3.org/2001/XMLSchema-instance» xsi:type=«Student»> <Name>Stacey</Name> </Person> |
При десериализации на основании значения этого атрибута десериализатор создаст объект нужного производного типа.
Именем, записываемым в XML атрибут type
, можно управлять, применяя к производному типу атрибут [XmlType]
:
[XmlType («Candidate»)] public class Student : Person { } |
XmlSerializer
автоматически рекурсивно обрабатывает объектные ссылки:
public class Person { public string Name; public Address HomeAddress = new Address(); } public class Address { public string Street, PostCode; } Person p = new Person(); p.Name = «Stacey»; p.HomeAddress.Street = «Odo St»; p.HomeAddress.PostCode = «6020»; |
Будет сериализовано:
<Person ... > <Name>Stacey</Name> <HomeAddress> <Street>Odo St</Street> <PostCode>6020</PostCode> </HomeAddress> </Person> |
Если на один и тот же объект ссылаются два поля, объект будет сериализован дважды.
Если поле ссылается на объект производного типа, можно управлять тем, какое имя получит соответствующий XML элемент. Например, для следующей ситуации:
public class Address { public string Street, PostCode; } public class USAddress : Address { } public class AUAddress : Address { } public class Person { public string Name; public Address HomeAddress = new USAddress(); } |
если зарегистрировать каждый производный класс, присвоив атрибуты [XmlInclude]
базовому классу:
[XmlInclude (typeof (AUAddress))] [XmlInclude (typeof (USAddress))] public class Address { public string Street, PostCode; } |
то имя XML элемента будет соответствовать имени ссылающегося поля или свойства, а реальный производный тип объекта будет записан в атрибут type
:
<Person ...> … <HomeAddress xsi:type=«USAddress»> … </HomeAddress> </Person> |
Если к ссылающемуся полю или свойству применить несколько атрибутов [XmlElement]
:
public class Person { public string Name; [XmlElement («Address», typeof (Address))] [XmlElement («AUAddress», typeof (AUAddress))] [XmlElement («USAddress», typeof (USAddress))] public Address HomeAddress = new USAddress(); } |
то каждый атрибут [XmlElement]
сопоставит имя элемента с типом и имя XML элемента будет совпадать с именем производного типа:
<Person ...> … <USAddress> … </USAddress> </Person> |
При этом если в атрибуте [XmlElement]
не указывать имя, а только тип, будет использовано стандартное имя типа (которое можно изменить с помощью [XmlType]
).
Сериализация коллекций
XmlSerializer
способен сериализовать коллекции без каких либо дополнительных настроек:
public class Person { public string Name; public List<Address> Addresses = new List<Address>(); } public class Address { public string Street, PostCode; } |
Будет сериализовано в:
<Person ... > <Name>…</Name> <Addresses> <Address> <Street>…</Street> <Postcode>…</Postcode> </Address> <Address> <Street>…</Street> <Postcode>…</Postcode> </Address> … </Addresses> </Person> |
Атрибут [XmlArray]
позволяет переименовать внешний элемент, а [XmlArrayItem]
— внутренние элементы:
public class Person { public string Name; [XmlArray («PreviousAddresses»)] [XmlArrayItem («Location»)] public List<Address> Addresses = new List<Address>(); } |
Будет сериализовано в:
<Person ... > <Name>…</Name> <PreviousAddresses> <Location> <Street>…</Street> <Postcode>…</Postcode> </Location> <Location> <Street>…</Street> <Postcode>…</Postcode> </Location> … </PreviousAddresses> </Person> |
Оба атрибута также позволяют указывать пространство имен.
Чтобы сериализовать коллекцию без внешнего элемента, необходимо к ссылающемуся на коллекцию полю добавить атрибут [XmlElement]
с аргументом имени, совпадающим с типом элементов коллекции:
public class Person { ... [XmlElement («Address»)] public List<Address> Addresses = new List<Address>(); } |
Если коллекция содержит элементы производных классов, то правил именования XML элементов следующие:
- чтобы включить имя производного типа в атрибут
type
нужно добавить атрибут[XmlInclude]
к базовому типу элементов коллекции (как это делалось выше):<Person ... >
<Name>…</Name>
<Addresses>
<Address xsi:type=«AUAddress»>
- если элементы коллекции должны именоваться в соответствии с их реальным типом (производным), необходимо применить несколько атрибутов
[XmlArrayItem]
или[XmlElement]
к ссылающемуся на коллекцию полю:<Person ... >
<Name>…</Name>
<!-начало необязательного внешнего элемента->
<AUAddress>
<Street>…</Street>
<Postcode>…</Postcode>
</AUAddress>
<USAddress>
<Street>…</Street>
<Postcode>…</Postcode>
</USAddress>
<!-конец необязательного внешнего элемента->
</Person>
При этом если использовать атрибут
[XmlArrayItem]
, внешний элемент будет включен в XML:[XmlArrayItem («Address», typeof (Address))]
[XmlArrayItem («AUAddress», typeof (AUAddress))]
[XmlArrayItem («USAddress», typeof (USAddress))]
public List<Address> Addresses = new List<Address>();
А если использовать атрибут
[XmlElement]
, внешний элемент будет исключен из коллекции:[XmlElement («Address», typeof (Address))]
[XmlElement («AUAddress», typeof (AUAddress))]
[XmlElement («USAddress», typeof (USAddress))]
public List<Address> Addresses = new List<Address>();
IXmlSerializable
Реализация интерфейса IXmlSerializable
дает значительно больше возможностей по управлению сериализацией и десериализацией, чем использование атрибутов. Объявление интерфейса выглядит следующим образом:
public interface IXmlSerializable { XmlSchema GetSchema(); void ReadXml (XmlReader reader); void WriteXml (XmlWriter writer); } |
Метод ReadXml
должен читать внешний начальный элемент, затем содержимое и внешний конечный элемент. Метод WriteXml
должен записывать только содержимое.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
using System; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; public class Address : IXmlSerializable { public string Street, PostCode; public XmlSchema GetSchema() { return null; } public void ReadXml(XmlReader reader) { reader.ReadStartElement(); Street = reader.ReadElementContentAsString («Street», «»); PostCode = reader.ReadElementContentAsString («PostCode», «»); reader.ReadEndElement(); } public void WriteXml (XmlWriter writer) { writer.WriteElementString («Street», Street); writer.WriteElementString («PostCode», PostCode); } } |
XmlSerializer
при сериализации и десериализации будет автоматически вызывать методы WriteXml
и ReadXml
.
Всем доброго времени суток, написал 2 метода 1-й сериализации, 2-й — десериализации, и при десериализации получаю следующие исключение:
Не удалось привести тип объекта «System.Int32» к типу
«binary.Classes.Serializ».
[Serializable]
class Serializ
{
public Int32 Line { get; set; }
BinaryFormatter formatter = new BinaryFormatter();
public void SerializSave (Int32 _line)
{
Line = _line;
using (FileStream fs = new FileStream(@"E:DELETEnr.dat", FileMode.Append))
{
formatter.Serialize(fs, Line);
Console.WriteLine("Объект сериализован");
}
}
public void SerializRead()
{
using (FileStream fs = new FileStream(@"E:DELETEnr.dat", FileMode.Open))
{
Serializ _serial = (Serializ)formatter.Deserialize(fs);
Console.WriteLine("Объект десериализован");
Console.WriteLine("Прочитано: {0}", _serial.Line);
}
}
}
Как исправить ошибку данного характера ? что я не так сделал ? спасибо
Спасибо за пример, но теперь я получаю такое исключение:
Additional information: Тип
«System.Runtime.Serialization.Formatters.Binary.BinaryFormatter» в
сборке «mscorlib, Version=4.0.0.0, Culture=neutral,
Пробовал и так FileMode.Append
и так FileMode.Create
Странно что на этот раз не так? и еще хотел спросить FileMode.Create
— даст мне дозапись данных в файл ? мне это важно. Спасибо
Ошибка сериализации пустых табличных частей |
Я |
vitek1
09.06.15 — 16:28
Ошибка заключается при десериализации объекта с пустой табличной частью, если в базе эта таб. часть заполнена. В этом случае заполненная таб. часть не очищается.
Поясню на примере.
Есть демо база, в ней док-т Поступление товаров и услуг с 2-ми таб. частями: Товары и Услуги (скрин Конфигуратор).
Для сериализации и десериализации использую коды (http://pixs.ru/showimage/RRRSRRSSRS_2575358_17611037.png):
[code]
&НаСервере
Процедура ВыгрузитьНаСервере()
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ИмяФайла, «windows-1251»);
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента(«Данные»);
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, Документ.ПолучитьОбъект());
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.Закрыть();
КонецПроцедуры
&НаКлиенте
Процедура Выгрузить(Команда)
ВыгрузитьНаСервере();
КонецПроцедуры
&НаСервере
Процедура ЗагрузитьНаСервере()
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ИмяФайла);
ЧтениеXML.ПерейтиКСодержимому();
ЧтениеXML.Прочитать();
ДокументОбъект = СериализаторXDTO.ПрочитатьXML(ЧтениеXML);
ДокументОбъект.ОбменДанными.Загрузка = Истина;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
ЧтениеXML.Прочитать();
ЧтениеXML.Закрыть();
Документ = ДокументОбъект.Ссылка;
КонецПроцедуры
&НаКлиенте
Процедура Загрузить(Команда)
ЗагрузитьНаСервере();
КонецПроцедуры
[code]
Сериализуем док-т с пустой таб. частью Товары, получаем xml:
[code]
<?xml version=»1.0″ encoding=»windows-1251″?>
<Данные>
<DocumentObject.ПоступлениеТоваровИУслуг xmlns=»http://v8.1c.ru/8.1/data/enterprise/current-config»; xmlns:xs=» http://www.w3.org/2001/XMLSchema »»» xmlns:xsi=» http://www.w3.org/2001/XMLSchem… »»»>
<Ref>3580c752-0e94-11e5-a111-c8d3a3841935</Ref>
<DeletionMark>false</DeletionMark>
<Date>2015-06-09T14:23:20</Date>
<Number>000000001</Number>
<Posted>true</Posted>
<Услуги>
<Номенклатура>3580c751-0e94-11e5-a111-c8d3a3841935</Номенклатура>
</Услуги>
</DocumentObject.ПоступлениеТоваровИУслуг>
</Данные>
[code]
далее добавляем одну или несколько строк в таб. вчасть Товары в этот же док-т. Загружаем полученный ранее xml файл. После оператора ДокументОбъект = СериализаторXDTO.ПрочитатьXML(ЧтениеXML); в отладчике видим, что ТЧ Товары действительно пустая (http://pixs.ru/showimage/RSRRRRR.pn_1376624_17611041.png). После строки Записать тоже у объекта Товары пустая (а Ссылка.Товары заполнена). Но после отработки кода таб. часть Товары остается заполненной (при просмотре в режиме предприятия или кодом выполнить ПолучитьОбъект()).
Никаких отмен транзакций нет, загружаемые поля из файла модифицируются (например, если в файле руками поменять номер, то после загрузки он действительно меняется).
Для эксперимента пробовал в xml файл руками добавить <Товары/> перед тэгом услуги, тогда ругается рпи чтении.
Выкладываю также саму базу.
Как при этом можно использовать механизм сериализации серез СериализаторXDTO, если он не очищает пустые таб. части при загрузке?
Можно обойти эту ошибку без «ручной» сериализации объектов.
PS. ЗаписатьXML (глобавльный контекст, а не метод СериализаторXDTO) работает нормально, он добавляет <Товары/>, но он не сериализует, например, таблицу значений и прочие типы.
MrStomak
1 — 09.06.15 — 16:32
XML и XDTO-сериализация объектов с табличными частями
Код ошибки: 30019662
Код(ы) обращения: SW915344, CSR-2226
Статус: Исправлена в будущей версии Зарегистрирована: 05.03.2015
Описание:
При чтении из XML (средствами XML и XDTO-сериализации) объектов с табличными частями, в случае, если в данных XML не содержится строк той или иной табличной части (табличная часть пустая), для существующих в БД объектов, при записи — очистки данной табличной части не произойдет.
Способ обхода:
Перед записью объекта для пустых табличных частей выполнять явную очистку при помощи метода Очистить.
vitek1
2 — 09.06.15 — 19:04
спасибо за ответ, очень помогли.
странно, что с 05.03.2015 1С не устранило ошибку, релизы-то выходили. Активно развивают и рекламируют универсальные кроссплатформенные средства обмена xml и json, но с этой ошибкой удобным и простым типовым механизмом пользоваться нельзя.
Для таких же несчастных, как я выкладываю универсальную процедуру, которую надо добавить после
ДокументОбъект = СериализаторXDTO.ПрочитатьXML(ЧтениеXML);
для обхода ошибки. Вызываем
ОчиститьПустыеТабличныеЧасти(ДокументОбъект );
Процедура ОчиститьПустыеТабличныеЧасти(Значение) Экспорт
Если ЭтоОбъект(Значение) Тогда
МетаданныеОбъекта = Значение.Метаданные();
Для каждого ТЧ Из МетаданныеОбъекта.ТабличныеЧасти Цикл
Если Значение[ТЧ.Имя].Количество() = 0 Тогда
Значение[ТЧ.Имя].Очистить();
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
// Проверка того, что переданный тип является объектым типом данных.
//
// Возвращаемое значение:
// Булево.
//
Функция ЭтоОбъект(Значение) Экспорт
Тип = XMLТипЗнч(Значение).ИмяТипа;
Возврат Лев(Тип, 14) = «CatalogObject.»
ИЛИ Лев(Тип, 15) = «DocumentObject.»
ИЛИ Лев(Тип, 19) = «ExchangePlanObject.»;
КонецФункции
у меня используется для мобильного приложения. Для настольного функцию ЭтоОбъект() расширить на остальные типы данных
LordXaosa, C# позволяет управлять процессом сериализации/десериализации.
Например, класс содержит вычисляемые поля и в целях экономии места нету смысла эти поля так же сохранять в файл — эти поля можно пометить атрибутом игнорирования (у каждого форматера свой, у BinaryFormatter — [NonSerialized]). А при восстановлении эти поля можно просто вычислить единожды для каждого экземпляра снова.
Для такой задачи достаточно унаследовать классом интерфейс IDeserializationCallback и определить его функцию
C# | ||
|
В момент, когда эта функция получает управление, все значимые поля уже содержат значения из файла и к ним можно спокойно обращаться. Пример использования этого интерфейса я дал в своем прошлом проекте .
Для задач, когда нужно перед записью в файл изменить значения некоторых значений (например зашифровать), существует другой интерфейс —
ISerializable и две его функции:
C# | ||
|
Вот маленький шаблонный пример как ими воспользоваться:
C# | ||
|
Для класса BinaryFormatter есть еще возможность назначить специальными атрибутами функции обработчики событий начала/окончания сериализации/десериализации…
Вообще-то тут можно много говорить по поводу способов управления процессом сериализации/десериализации, но зачем повторять то, что давно уже написано ?
Можно покопать по этому поводу MSDN или вот [] книгу, раздел 5, глава 3 (а можно и первые 2 тоже ). Я изучал эту тему именно по этой книге.
С классом CryptoStream не сталкивался, ничего подсказать не могу. Может завтра поковыряюсь с ним.
1
Как не наступить на грабли, работая с сериализацией
Время на прочтение
17 мин
Количество просмотров 30K
Несмотря на то, что использовать механизм сериализации при программировании на C# достаточно просто и удобно, есть моменты, которые стоит учитывать. О том, на какие грабли можно наступить, работая с сериализацией, о примерах кода, в котором эти грабли припрятаны, а также о том, как PVS-Studio поможет вам избежать шишек на лбу, и будет эта статья.
Для кого статья?
Данная статья будет особенно полезна разработчикам, только начинающим знакомиться с механизмом сериализации. Более опытные программисты также могут почерпнуть для себя что-то интересное, или просто убедиться, что даже профессионалы порой допускают ошибки.
Тем не менее, подразумевается, что читатель уже знаком с механизмом сериализации.
При чём тут PVS-Studio? В релизе 6.05 были добавлены 6 диагностических правил, обнаруживающих подозрительный код, связанный с использованием механизма сериализации. Эти диагностики в основном ищут проблемные места, связанные с атрибутом [Serializable] или реализацией интерфейса ISerializable.
Примечание.
Стоит понимать, что описанные в статье утверждения актуальны для некоторых сериализаторов, например — BinaryFormatter и SoapFormatter, а для других, например, собственноручно написанного сериализатора, поведение может отличаться. Например, отсутствие атрибута [Serializable] у класса может не помешать проводить его сериализацию и десериализацию собственным сериализатором.
Кстати, если вы работаете с сериализацией, советую загрузить пробную версию анализатора и проверить свой код на наличие подозрительных мест.
Реализуя ISerializable, не забудьте про конструктор сериализации
Реализация типом интерфейса ISerializable позволяет управлять сериализацией, выбирая, какие члены нужно сериализовать, какие — нет, какие значение нужно записывать при сериализации членов и т.п.
Интерфейс ISerializable содержит объявление одного метода — GetObjectData, который будет вызван при сериализации объекта. Но в паре с этим методом обязательно должен быть реализован конструктор, который будет вызываться при десериализации объекта. Так как интерфейс не может обязать вас реализовать в своем типе какой-то конструктор, эта задача ложится на плечи программиста, занимающегося реализацией сериализуемого типа. Конструктор сериализации имеет следующую сигнатуру:
Ctor(SerializationInfo, StreamingContext)
Без наличия данного конструктора сериализация объекта пройдёт успешно (при условии корректной реализации метода GetObjectData), но восстановить (десериализовать) его не удастся — будет сгенерировано исключение типа SerializationException.
Посмотрим на пример подобного кода из проекта Glimpse:
[Serializable]
internal class SerializableTestObject : ISerializable
{
public string TestProperty { get; set; }
public void GetObjectData(SerializationInfo info,
StreamingContext context)
{
info.AddValue("TestProperty", this.TestProperty);
}
}
Предупреждение PVS-Studio: V3094 Possible exception when deserializing. The SerializableTestObject(SerializationInfo, StreamingContext) constructor is missing. Glimpse.Test.AspNet SessionModelConverterShould.cs 111
Сериализация экземпляра данного класса пройдёт успешно, а вот при десериализации возникнет исключение, так как нет соответствующего конструктора. Скорее всего, это не ошибка (исходя из названия класса и файла), но в качестве иллюстрации описанной ситуации — то, что нужно.
Конструктор сериализации для данного класса мог бы выглядеть так:
protected SerializableTestObject(SerializationInfo info,
StreamingContext context)
{
TestProperty = info.GetString(nameof(TestProperty));
}
Обращайте внимание на модификатор доступа конструктора сериализации
При разработке типа, реализующего интерфейс ISerializable, важно правильно определить модификатор доступа для конструктора сериализации. Здесь возможны несколько случаев:
- конструктор сериализации объявлен с модификатором private в незапечатанном классе;
- конструктор сериализации объявлен с модификатором доступа public или internal;
- конструктор сериализации объявлен с модификатором protected в запечатанном классе.
Наибольший интерес представляет первый из приведённых выше вариантов, так как он таит в себе наибольшую опасность. Кратко остановимся и на втором пункте, а третий рассматривать не будем — объявить член с модификатором protected в структуре не даст компилятор (ошибка компиляции), если такой член объявляется в запечатанном классе — компилятор выдаст предупреждение.
Конструктор сериализации в незапечатанном классе имеет модификатор доступа ‘private’
Это наиболее опасный случай из ситуаций неправильного применения модификаторов доступа к конструкторам сериализации. Если тип является незапечатанным, подразумевается, что у него могут быть наследники. Однако если конструктор сериализации имеет модификатор доступа private, он не сможет быть вызван из дочернего класса.
В таком случае у разработчика дочернего класса есть 2 выхода — отказаться от использования данного родительского класса или вручную десериализовать члены базового класса. Стоит отметить, что второй случай навряд ли можно считать решением проблемы:
- не факт, что в базовом классе была предусмотрена тривиальная десериализация членов;
- разработчик дочернего класса может забыть десереализовать какой-либо член базового класса;
- при всём желании, десериализовать приватные члены базового класса не удастся.
Поэтому, разрабатывая незапечатанный сериализуемый класс, обращайте внимание на то, какой модификатор доступа имеет конструктор сериализации.
При анализе проектов удалось найти несколько таких, в которых описанное выше правило не соблюдалось.
NHibernate
[Serializable]
public class ConnectionManager : ISerializable,
IDeserializationCallback
{
....
private ConnectionManager(SerializationInfo info,
StreamingContext context)
{
....
}
....
}
Предупреждение PVS-Studio: V3103 A private Ctor(SerializationInfo, StreamingContext) constructor in unsealed type will not be accessible when deserializing derived types. NHibernate ConnectionManager.cs 276
Roslyn
[Serializable]
private class TestDiagnostic : Diagnostic, ISerializable
{
....
private TestDiagnostic (SerializationInfo info,
StreamingContext context)
{
....
}
....
}
Предупреждение PVS-Studio: V3103 A private TestDiagnostic(SerializationInfo, StreamingContext) constructor in unsealed type will not be accessible when deserializing derived types. DiagnosticAnalyzerTests.cs 100
В обоих примерах, приведённых выше, у конструктора сериализации следовало установить модификатор доступа protected, чтобы дочерние классы могли вызвать его при десериализации.
Не объявляйте конструктор сериализации с модификаторами ‘public’ или ‘internal’
Это совет хорошего стиля программирования. Объявление конструктора сериализации с модификатором public или internal не приведёт к ошибке, но смысла в этом нет — данный конструктор не должен вызываться извне, а сериализатору без разницы, какой модификатор доступа имеет конструктор.
При проверке open source проектов встретились несколько таких, в которых это правило не соблюдалось.
MSBuild
[Serializable]
private sealed class FileState : ISerializable
{
....
internal SystemState(SerializationInfo info,
StreamingContext context)
{
....
}
....
}
Предупреждение PVS-Studio: V3103 The Ctor(SerializationInfo, StreamingContext) constructor should be used for deserialization. Making it internal is not recommended. Consider making it private. Microsoft.Build.Tasks SystemState.cs 218
[Serializable]
private sealed class FileState : ISerializable
{
....
internal FileState(SerializationInfo info, StreamingContext context)
{
....
}
....
}
Предупреждение PVS-Studio: V3103 The Ctor(SerializationInfo, StreamingContext) constructor should be used for deserialization. Making it internal is not recommended. Consider making it private. Microsoft.Build.Tasks SystemState.cs 139
В обоих случаях для конструктора сериализации следовало установить модификатор доступа private, так оба класса, приведённых выше, являются запечатанными.
NHibernate
[Serializable]
public class StatefulPersistenceContext : IPersistenceContext,
ISerializable,
IDeserializationCallback
{
....
internal StatefulPersistenceContext(SerializationInfo info,
StreamingContext context)
{
....
}
....
}
Предупреждение PVS-Studio: V3103 The Ctor(SerializationInfo, StreamingContext) constructor should be used for deserialization. Making it internal is not recommended. Consider making it protected. NHibernate StatefulPersistenceContext.cs 1478
[Serializable]
public class Configuration : ISerializable
{
....
public Configuration(SerializationInfo info,
StreamingContext context)
{
....
}
....
}
Предупреждение PVS-Studio: V3103 The Ctor(SerializationInfo, StreamingContext) constructor should be used for deserialization. Making it public is not recommended. Consider making it protected. NHibernate Configuration.cs 84
Ввиду того, что оба класса являются незапечатанными, для конструкторов сериализации следовало установить модификатор доступа protected.
Реализуйте виртуальный метод GetObjectData в незапечатанных классах
Правило простое — если вы разрабатываете незапечатанный класс, реализующий интерфейс ISerializable, объявите метод GetObjectData с модификатором virtual. Это позволит дочерним классам корректно производить сериализацию объекта при использовании полиморфизма.
Чтобы лучше понять суть проблемы, предлагаю рассмотреть несколько примеров.
Допустим, у нас есть следующие объявления родительского и дочернего классов.
[Serializable]
class Base : ISerializable
{
....
public void GetObjectData(SerializationInfo info,
StreamingContext context)
{
....
}
}
[Serializable]
sealed class Derived : Base
{
....
public new void GetObjectData(SerializationInfo info,
StreamingContext context)
{
....
}
}
Предположим, что имеется метод сериализации и десереализации объекта следующего вида:
void Foo(BinaryFormatter bf, MemoryStream ms)
{
Base obj = new Derived();
bf.Serialize(ms, obj);
ms.Seek(0, SeekOrigin.Begin);
Derived derObj = (Derived)bf.Deserialize(ms);
}
В таком случае сериализация выполнится неправильно из-за того, что будет вызван метод GetObjectData не дочернего, а родительского класса. Следовательно, члены дочернего класса не будут сериализованы. Если при десериализации из объекта типа SerializationInfo будут извлекаться значения членов, добавляемых в методе GetObjectData дочернего класса, будет сгенерировано исключение, так как объект типа SerializationInfo не будет содержать запрашиваемых ключей.
Для исправления ошибки в родительском классе к методу GetObjectData необходимо добавить модификатор virtual, в производном — override.
Если в родительском классе присутствует только явная реализация интерфейса ISerializable, добавить к ней модификатор virtual вы не сможете. Однако оставив всё, как есть, вы рискуете усложнить жизнь разработчикам дочерних классов.
Рассмотрим пример реализации родительского и дочернего классов:
[Serializable]
class Base : ISerializable
{
....
void ISerializable.GetObjectData(SerializationInfo info,
StreamingContext context)
{
....
}
}
[Serializable]
sealed class Derived : Base, ISerializable
{
....
public void GetObjectData(SerializationInfo info,
StreamingContext context)
{
....
}
}
В таком случае из дочернего класса будет невозможно обратиться к методу GetObjectData родительского класса. И если в базовом методе сериализуются приватные члены, обратиться к ним из дочернего класса также не удастся, а значит и не удастся провести корректную сериализацию. Для исправления ошибки помимо явной реализации в базовый класс необходимо добавить неявную реализацию виртуального метода GetObjectData. Тогда исправленный код может выглядеть так:
[Serializable]
class Base : ISerializable
{
....
void ISerializable.GetObjectData(SerializationInfo info,
StreamingContext context)
{
GetObjectData(info, context);
}
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context)
{
....
}
}
[Serializable]
sealed class Derived : Base
{
....
public override void GetObjectData(SerializationInfo info,
StreamingContext context)
{
....
base.GetObjectData(info, context);
}
}
Или же, если не подразумевается наследование данного класса, следует сделать его запечатанным, добавив к объявлению класса модификатор sealed.
Roslyn
[Serializable]
private class TestDiagnostic : Diagnostic, ISerializable
{
private readonly string _kind;
....
private readonly string _message;
....
void ISerializable.GetObjectData(SerializationInfo info,
StreamingContext context)
{
info.AddValue("id", _descriptor.Id);
info.AddValue("kind", _kind);
info.AddValue("message", _message);
info.AddValue("location", _location, typeof(Location));
info.AddValue("severity", _severity, typeof(DiagnosticSeverity));
info.AddValue("defaultSeverity", _descriptor.DefaultSeverity,
typeof(DiagnosticSeverity));
info.AddValue("arguments", _arguments, typeof(object[]));
}
....
}
Предупреждение PVS-Studio: V3104 ‘GetObjectData’ implementation in unsealed type ‘TestDiagnostic’ is not virtual, incorrect serialization of derived type is possible. CSharpCompilerSemanticTest DiagnosticAnalyzerTests.cs 112
Класс TestDiagnostic является незапечатанным (хоть и приватным, так что унаследоваться от него в рамках того же класса возможно), но при этом он имеет только явную реализацию интерфейса ISerializable, в которой, ко всему прочему, сериализуются приватные члены. Это означает одно — разработчик дочернего класса никаким образом не сможет сериализовать необходимые члены: метод GetObjectData будет ему недоступен, а обратиться к членам напрямую не позволит модификатор доступа.
Правильнее было бы вынести весь код сериализации, приведённый выше, в виртуальный метод GetObjectData, на который сослаться из явной реализации интерфейса:
void ISerializable.GetObjectData(SerializationInfo info,
StreamingContext context)
{
GetObjectData(info, context);
}
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context)
{
info.AddValue("id", _descriptor.Id);
info.AddValue("kind", _kind);
info.AddValue("message", _message);
info.AddValue("location", _location, typeof(Location));
info.AddValue("severity", _severity, typeof(DiagnosticSeverity));
info.AddValue("defaultSeverity", _descriptor.DefaultSeverity,
typeof(DiagnosticSeverity));
info.AddValue("arguments", _arguments, typeof(object[]));
}
Все сериализуемые члены должны иметь сериализуемый тип
Это условие является обязательным для корректной сериализации объекта вне зависимости от того, происходит ли автоматическая сериализация (когда тип декорирован атрибутом [Serializable] и при этом не реализует интерфейс ISerializable) или сериализация осуществляется вручную (реализован ISerializable).
В противном случае, если при сериализации встретится член, не декорированный атрибутом [Serializable], будет сгенерировано исключение типа SerializationException.
Если требуется сериализовать объект без учёта членов, имеющих несереализуемый тип, возможны несколько подходов:
- сделайте несериализуемый тип сериализуемым;
- если происходит автоматическая сериализация, декорируйте поля, которые не нужно сериализовать, атрибутом [NonSerialized];
- если происходит ручная сериализация, просто игнорируйте те члены, которые вам не нужны.
Стоит обратить внимание на тот факт, что атрибут [NonSerialized] применим только к полям. Таким образом, вы не сможете запретить сериализацию свойства, но, если оно будет иметь несериализуемый тип — получите исключение. Например, при попытке сериализации класса SerializedClass, определение которого приведено ниже:
sealed class NonSerializedType { }
[Serializable]
sealed class SerializedClass
{
private Int32 value;
public NonSerializedType NSProp { get; set; }
}
Обойти эту ситуацию можно, реализовав свойство через поле, декорированное атрибутом [NonSerialized]:
[Serializable]
sealed class SerializedClass
{
private Int32 value;
[NonSerialized]
private NonSerializedType nsField;
public NonSerializedType NSProp
{
get { return nsField; }
set { nsField = value; }
}
}
Подобные ошибки, когда сериализуемый тип имеет члены несереализуемых типов, не декорированные атрибутом [NonSerialized], обнаруживает диагностическое правило V3097 статического анализатора кода PVS-Studio.
Напоминаю, что данное предупреждение не обязательно свидетельствует о наличии ошибки — всё зависит от используемого сериализатора.
Рассмотрим несколько примеров кода, в которых описанное условие было нарушено.
Subtext
public class BlogUrlHelper
{
....
}
[Serializable]
public class AkismetSpamService : ICommentSpamService
{
....
readonly BlogUrlHelper _urlHelper;
....
}
Предупреждение PVS-Studio: V3097 Possible exception: the ‘AkismetSpamService’ type marked by [Serializable] contains non-serializable members not marked by [NonSerialized]. Subtext.Framework AkismetSpamService.cs 31
Тип BlogUrlHelper поля _urlHelper не является сериализуемым, поэтому при попытке сериализации экземпляра класса AkismetSpamService некоторыми сериализаторами, будет сгенерировано исключение типа SerializationException. Решать проблему нужно, отталкиваясь от ситуации. Если используются сериализаторы типа BinaryFormatter или SoapFormatter — необходимо либо декорировать поле атрибутом [NonSerialized], либо декорировать атрибутом [Serializable] тип BlogUrlHepler. Если используются другие сериализаторы, не требующие наличия атрибута [Serializable] у сериализуемых полей, можно не забивать голову.
NHibernate
public class Organisation
{
....
}
[Serializable]
public class ResponsibleLegalPerson
{
....
private Organisation organisation;
....
}
Предупреждение PVS-Studio: V3097 Possible exception: the ‘ResponsibleLegalPerson’ type marked by [Serializable] contains non-serializable members not marked by [NonSerialized]. NHibernate.Test ResponsibleLegalPerson.cs 9
Ситуация аналогична описанной выше — или пан, или пропал. Всё зависит от используемого сериализатора.
Не забывайте про атрибут [Serializable] при реализации интерфейса ISerializable
Данный совет относится скорее к тем, кто только начинает работать с сериализацией. Управляя сериализацией вручную, посредством реализации интерфейса ISerializable, легко забыть декорировать тип атрибутом [Serializable], что потенциально приводит к генерации исключения типа SerializationException. Такие сериализаторы, как BinaryFormatter, требуют наличия данного атрибута.
SharpDevelop
Интересные примеры данной ошибки встретились в проекте SharpDevelop.
public class SearchPatternException : Exception, ISerializable
{
....
protected SearchPatternException(SerializationInfo info,
StreamingContext context)
: base(info, context)
{
}
}
Предупреждение PVS-Studio: V3096 Possible exception when serializing ‘SearchPatternException’ type. [Serializable] attribute is missing. ICSharpCode.AvalonEdit ISearchStrategy.cs 80
public class DecompilerException : Exception, ISerializable
{
....
protected DecompilerException(SerializationInfo info,
StreamingContext context)
: base(info, context)
{
}
}
Предупреждение PVS-Studio: V3096 Possible exception when serializing ‘DecompilerException’ type. [Serializable] attribute is missing. ICSharpCode.Decompiler DecompilerException.cs 28
Для передачи объекта исключения между доменами приложений происходит его сериализация и десериализация. Соответственно, собственные типы исключений должны быть сериализуемыми. В приведённых выше примерах типы SearchPatternException и DecompilerException наследуются от Exception и реализуют конструкторы сериализации, но при этом не декорированы атрибутом [Serializable], а значит, что при попытке сериализации объектов данных типов (например, для передачи между доменами) будет сгенерировано исключение типа SerializationException. Таким образом, например, генерируя исключение в другом домене приложений, в текущем вы перехватите не сгенерированное исключение, а SerializationException.
Убедитесь, что в методе GetObjectData сериализуются все необходимые члены типа
Реализуя интерфейс ISerializable и определяя метод GetObjectData, вы берёте на себя ответственность за то, какие члены типа будут сериализованы и какие значения в них будут записаны. В этом случае для разработчиков открывается большой простор в управлении сериализацией: в качестве сериализуемого значения, ассоциированного с членом (а если быть более честным — с любой строкой) вы можете записать действительное значение сериализованного объекта, результат работы какого-либо метода, константное или литеральное значение — всё, что захотите.
Однако в данном случае на плечи разработчика ложится большая ответственность, потому что необходимо не забыть никакой член, подлежащий сериализации, даже если он находится в базовом классе. Все мы люди, так что иногда некоторые члены всё же забываются.
Для диагностирования таких ситуаций в статическом анализаторе кода PVS-Studio предусмотрено диагностическое правило V3099. Предлагаю ознакомиться с некоторыми примерами кода, обнаруженных данным правилом.
SharpDevelop
[Serializable]
public abstract class XshdElement
{
public int LineNumber { get; set; }
public int ColumnNumber { get; set; }
public abstract object AcceptVisitor(IXshdVisitor visitor);
}
[Serializable]
public class XshdColor : XshdElement, ISerializable
{
....
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context)
{
if (info == null)
throw new ArgumentNullException("info");
info.AddValue("Name", this.Name);
info.AddValue("Foreground", this.Foreground);
info.AddValue("Background", this.Background);
info.AddValue("HasUnderline", this.Underline.HasValue);
if (this.Underline.HasValue)
info.AddValue("Underline", this.Underline.Value);
info.AddValue("HasWeight", this.FontWeight.HasValue);
if (this.FontWeight.HasValue)
info.AddValue("Weight", this.FontWeight
.Value
.ToOpenTypeWeight());
info.AddValue("HasStyle", this.FontStyle.HasValue);
if (this.FontStyle.HasValue)
info.AddValue("Style", this.FontStyle.Value.ToString());
info.AddValue("ExampleText", this.ExampleText);
}
}
Предупреждение PVS-Studio: V3099 Not all the members of ‘XshdColor’ type are serialized inside ‘GetObjectData’ method: LineNumber, ColumnNumber. ICSharpCode.AvalonEdit XshdColor.cs 101
В этом коде нет проблем, описанных ранее, таких как неправильные модификаторы доступа у конструктора сериализации, отсутствие атрибута [Serializable] или модификатора virtual у метода GetObjectData.
Увы, ошибка здесь всё равно есть. В методе GetObjectData не учитываются свойства базового класса, а значит, при сериализации часть данных будет потеряна. В итоге, при десериализации будет восстановлен объект с другим состоянием.
В данном случае решением проблемы будет ручное добавление необходимых значений, например, таким образом:
info.AddValue(nameof(LineNumber), LineNumber);
info.AddValue(nameof(ColumnNumber), ColumnNumber);
Если бы базовый класс также реализовывал интерфейс ISerializable, решение было бы более элегантным — вызовом в производном методе GetObjectData базового.
NHibernate
[Serializable]
public sealed class SessionImpl : AbstractSessionImpl,
IEventSource,
ISerializable,
IDeserializationCallback
{
....
void ISerializable.GetObjectData(SerializationInfo info,
StreamingContext context)
{
log.Debug("writting session to serializer");
if (!connectionManager.IsReadyForSerialization)
{
throw new InvalidOperationException("Cannot serialize a Session
while connected");
}
info.AddValue("factory", Factory, typeof(SessionFactoryImpl));
info.AddValue("persistenceContext", persistenceContext,
typeof(StatefulPersistenceContext));
info.AddValue("actionQueue", actionQueue, typeof(ActionQueue));
info.AddValue("timestamp", timestamp);
info.AddValue("flushMode", flushMode);
info.AddValue("cacheMode", cacheMode);
info.AddValue("interceptor", interceptor, typeof(IInterceptor));
info.AddValue("enabledFilters", enabledFilters,
typeof(IDictionary<string, IFilter>));
info.AddValue("enabledFilterNames", enabledFilterNames,
typeof(List<string>));
info.AddValue("connectionManager", connectionManager,
typeof(ConnectionManager));
}
....
private string fetchProfile;
....
}
Предупреждение PVS-Studio: V3099 Not all the members of ‘SessionImpl’ type are serialized inside ‘GetObjectData’ method: fetchProfile. NHibernate SessionImpl.cs 141
На этот раз забыли сериализовать поле текущего класса (fetchProfile). Как видно из определения, оно не декорировано атрибутом [NonSerialized] (в отличии от других полей, не сериализуемых в методе GetObjectData).
В данном проекте нашлось ещё два подобных места:
- V3099 Not all the members of ‘Configuration’ type are serialized inside ‘GetObjectData’ method: currentDocumentName, preMappingBuildProcessed. NHibernate Configuration.cs 127
- V3099 Not all the members of ‘ConnectionManager’ type are serialized inside ‘GetObjectData’ method: flushingFromDtcTransaction. NHibernate ConnectionManager.cs 290
С подобными ошибками связана интересная особенность — они либо приводят к генерации исключения, либо к трудноуловимым логическим ошибкам.
Исключение будет сгенерировано в том случае, если в конструкторе сериализации попытаются получить значение того поля, которое не было добавлено (обратятся по отсутствующему ключу). Если же про член забыли совсем (и в методе GetObjectData, и в конструкторе сериализации), будет происходить порча состояния объекта.
Обобщение
Кратко обобщив всю информацию, приведённую выше, можно сформулировать несколько советов и правил:
- Декорируйте атрибутом [Serializable] типы, реализующие интерфейс ISerializable;
- Убедитесь, что все сериализуемые члены декорированы атрибутом [Serializable] и корректно сериализуются;
- Реализуя интерфейс ISerializable, не забудьте реализовать конструктор сериализации (Ctor(SerializationInfo, StreamingContext));
- В запечатанных типах установите модификатор доступа private для конструктора сериализации, в незапечатанных — protected;
- В незапечатанных типах, реализующий интерфейс ISerializable, сделайте метод GetObjectData виртуальным;
- Проверьте, что в методе GetObjectData сериализуются все необходимые члены, включая члены базового типа, если он есть.
Заключение
Надеюсь, вы узнали что-то новое из статьи и стали большим экспертом в вопросах сериализации. Придерживаясь приведённых выше советов и правил, вы сможете тратить меньше времени на отладку, облегчить жизнь себе и другим разработчикам, работающим с вашими классами. А анализатор PVS-Studio ещё больше облегчит жизнь, позволяя выявлять подобные ошибки сразу после их появления.
Дополнительная информация
- V3094. Possible exception when deserializing type. The Ctor(SerializationInfo, StreamingContext) constructor is missing
- V3096. Possible exception when serializing type. [Serializable] attribute is missing
- V3097. Possible exception: type marked by [Serializable] contains non-serializable members not marked by [NonSerialized]
- V3099. Not all the members of type are serialized inside ‘GetObjectData’ method
- V3103. A private Ctor(SerializationInfo, StreamingContext) constructor in unsealed type will not be accessible when deserializing derived types
- V3104. ‘GetObjectData’ implementation in unsealed type is not virtual, incorrect serialization of derived type is possible
- MSDN. Serialization in the .NET Framework
- MSDN. Custom Serialization
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Sergey Vasiliev. How to not shoot yourself in the foot when working with serialization.
LordXaosa, C# позволяет управлять процессом сериализации/десериализации.
Например, класс содержит вычисляемые поля и в целях экономии места нету смысла эти поля так же сохранять в файл — эти поля можно пометить атрибутом игнорирования (у каждого форматера свой, у BinaryFormatter — [NonSerialized]). А при восстановлении эти поля можно просто вычислить единожды для каждого экземпляра снова.
Для такой задачи достаточно унаследовать классом интерфейс IDeserializationCallback и определить его функцию
C# | ||
|
В момент, когда эта функция получает управление, все значимые поля уже содержат значения из файла и к ним можно спокойно обращаться. Пример использования этого интерфейса я дал в своем прошлом проекте .
Для задач, когда нужно перед записью в файл изменить значения некоторых значений (например зашифровать), существует другой интерфейс —
ISerializable и две его функции:
C# | ||
|
Вот маленький шаблонный пример как ими воспользоваться:
C# | ||
|
Для класса BinaryFormatter есть еще возможность назначить специальными атрибутами функции обработчики событий начала/окончания сериализации/десериализации…
Вообще-то тут можно много говорить по поводу способов управления процессом сериализации/десериализации, но зачем повторять то, что давно уже написано ?
Можно покопать по этому поводу MSDN или вот [] книгу, раздел 5, глава 3 (а можно и первые 2 тоже ). Я изучал эту тему именно по этой книге.
С классом CryptoStream не сталкивался, ничего подсказать не могу. Может завтра поковыряюсь с ним.
1
I have the following code:
class Program
{
static void Main(string[] args)
{
string xml = @"<ArrayOfUserSetting>
<UserSetting>
<Value>Proposals</Value>
<Name>LastGroup</Name>
</UserSetting>
<UserSetting>
<Value>Visible</Value>
<Name>WidgetsVisibility</Name>
</UserSetting>
</ArrayOfUserSetting>";
List<UserSetting> settings =
GetObjFromXmlDocument<List<UserSetting>>(xml);
}
public static T GetObjFromXmlDocument<T>(string xml)
{
T customType;
XmlSerializer serializer = new XmlSerializer(typeof(T));
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
using (XmlNodeReader xmlNodeReader = new XmlNodeReader(xmlDocument))
{
customType = (T)serializer.Deserialize(xmlNodeReader);
}
return customType;
}
}
[Serializable]
public class UserSetting
{
public string Value { get; set; }
public string Name { get; set; }
}
The code works fine and the call to GetObjFromXmlDocument yields a List collection. However, I always get a first chance exception of type System.IO.FileNotFoundException
in mscorlib.dll, when XmlSerializer serializer = new XmlSerializer(typeof(T));
is executed.
So I went into Debug/Exception and turned on Managed Debugging Assistants. I got the following on that line:
The assembly with display name ‘mscorlib.XmlSerializers’ failed to load in the ‘LoadFrom’ binding context of the AppDomain with ID 1. The cause of the failure was: System.IO.FileNotFoundException: Could not load file or assembly ‘mscorlib.XmlSerializers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’ or one of its dependencies. The system cannot find the file specified.
File name: ‘mscorlib.XmlSerializers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’
Can someone explain why this is happening? Is there something I could do to the UserSetting
class to make the problem disappear? The application is quite performance sensitive and I’d rather not have the exception.
Сериализация — это автоматический процесс преобразования структур данных или состояний объектов в формат, который Unity может сохранять и реконструировать позже. (Дополнительную информацию см. в документации по сериализации сценариев.)
В некоторых случаях сериализация скрипта может вызвать ошибки. Исправления некоторых из них перечислены ниже.
Вызов Unity Scripting API из конструктора или инициализаторов полей
Вызов Scripting API, например GameObject.Find внутри MonoBehaviour конструктор или инициализатор поля вызывает ошибку: «Find нельзя вызывать из конструктора MonoBehaviour (или инициализатора поля экземпляра), вместо этого вызовите его в Awake или Start».
Исправьте это, выполнив вызов Scripting API в MonoBehaviour.Start, а не в конструкторе.
Вызов Unity Scripting API во время десериализации
Вызов Scripting API, такого как GameObject.Find, из конструктора класса, отмеченного System.Serializable
вызывает ошибку: «Find нельзя вызывать во время сериализации, вместо этого вызовите его из Awake или Start».
Чтобы исправить это, отредактируйте свой код, чтобы он не вызывал вызовы Scripting API в любых конструкторах для любых сериализованных объектов.
Потокобезопасный API скриптов Unity
На большую часть Scripting API распространяются перечисленные выше ограничения. Исключением являются только некоторые части API сценариев Unity, которые можно вызывать из любого места. Это:
-
Debug.Log
-
Mathf функции
-
Простые автономные структуры; например, математические структуры, такие как Vector3 и QuaternionСтандартный способ Unity для представления поворотов в виде данных. При написании кода, имеющего дело с поворотами, обычно следует использовать класс Quaternion и его методы. Подробнее
См. в Словарь
Чтобы снизить риск ошибок во время сериализации, вызывайте только автономные методы API, которым не нужно получать или устанавливать данные в самом Unity. Вызывайте их только в том случае, если нет альтернативы.
I am saving a treeview to an xml file then i got this error, the xml saves but Im still bothered about this error so can anyone tell me what this means? thank you
Type ‘System.Web.UI.WebControls.TreeNode’ in Assembly ‘System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ is not marked as serializable.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Runtime.Serialization.SerializationException: Type ‘System.Web.UI.WebControls.TreeNode’ in Assembly ‘System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ is not marked as serializable.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[SerializationException: Type 'System.Web.UI.WebControls.TreeNode' in Assembly 'System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' is not marked as serializable.] System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type) +9476053 System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context) +247 System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo() +160 System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder) +491 System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.Serialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder) +54 System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck) +542 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck) +133 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph) +13 System.Web.UI.ObjectStateFormatter.SerializeValue(SerializerBinaryWriter writer, Object value) +3113 [ArgumentException: Error serializing value 'System.Web.UI.WebControls.TreeNode' of type 'System.Web.UI.WebControls.TreeNode.'] System.Web.UI.ObjectStateFormatter.SerializeValue(SerializerBinaryWriter writer, Object value) +3428 System.Web.UI.ObjectStateFormatter.Serialize(Stream outputStream, Object stateGraph) +141 System.Web.UI.ObjectStateFormatter.Serialize(Object stateGraph) +57 System.Web.UI.ObjectStateFormatter.System.Web.UI.IStateFormatter.Serialize(Object state) +4 System.Web.UI.Util.SerializeWithAssert(IStateFormatter formatter, Object stateGraph) +37 System.Web.UI.HiddenFieldPageStatePersister.Save() +79 System.Web.UI.Page.SavePageStateToPersistenceMedium(Object state) +108 System.Web.UI.Page.SaveAllState() +315 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2839
Some more examples
Here are some further minimal examples that might be of interest in addition to the one provided in A.H.’s answer. I’m using this test setup to conveniently run them on PostgreSQL 13.5.
Common setup:
0: CREATE TABLE "MyInt"(i INTEGER);
0: INSERT INTO foo VALUES(0);
The first thing I’d like to point out is that even read-only statements like SELECT
can matter.
For example, this example does not raise any errors:
0: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ
1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ
0: UPDATE "MyInt" SET i = 1
0: COMMIT
1: UPDATE "MyInt" SET i = 2
1: COMMIT
The only difference between this example and the one in the aforementioned answer is that the one in that answer is that in this one thread 1 does the UPDATE
after the COMMIT
of thread 0. TODO why does it matter?
However, if we simply add a SELECT
from thread 1 before thread 0 commits as in:
0: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ
1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ
0: UPDATE "MyInt" SET i = 1
1: SELECT * FROM "MyInt"
0: COMMIT
1: UPDATE "MyInt" SET i = 2
1: COMMIT
then the last UPDATE
blows up with:
could not serialize access due to concurrent update
TODO understand exactly why the SELECT
matters/how it is tracked by PostgreSQL. What seems to happen is that PostgreSQL makes considerations of type:
could such database serialization property possibly be violated if the client had such and such information?
which requires it to also track SELECT
statements.
Example that blows up only on SERIALIZABLE
Here is another interesting example acting on two different rows. This example blows up only on SERIALIZABLE
, but not on REPEATABLE READ
(TODO why):
INSERT INTO "MyInt" VALUES (0)
INSERT INTO "MyInt" VALUES (10)
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE
UPDATE "MyInt" SET i = 1 WHERE i = 0
SELECT * FROM "MyInt"
COMMIT
UPDATE "MyInt" SET i = 11 WHERE i = 10
COMMIT
The error message in this case is:
could not serialize access due to read/write dependencies among transactions
Unfortunately, after hours staring at https://www.postgresql.org/docs/13/transaction-iso.html#XACT-REPEATABLE-READ I’m still unable to precisely explain why some of those blow up and others don’t, but I felt that the examples were of enough interest to publish regardless, maybe someone can clarify the exact sequence of steps taken by PostgreSQL in future edits/comments.
Examples from the tests
Another source of many such examples is, unsurprisingly, the in-tree tests under src/test/isolation, and they are quite readable too. A grep for could not serialize
Files under src/test/isolation/specs/*.spec determine which steps are to be done, and corresponding files under src/test/isolation/expected/*.out contain the exact expected psql
raw output. So we can just read the .out
files to see many full examples of failures.
For example src/test/isolation/expected/insert-conflict-do-nothing-2.out contains:
starting permutation: beginrr1 beginrr2 donothing1 donothing2 c1 c2 show
step beginrr1: BEGIN ISOLATION LEVEL REPEATABLE READ;
step beginrr2: BEGIN ISOLATION LEVEL REPEATABLE READ;
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING; <waiting ...>
step c1: COMMIT;
step donothing2: <... completed>
ERROR: could not serialize access due to concurrent update
step c2: COMMIT;
step show: SELECT * FROM ints;
key|val
---+----------
1|donothing1
(1 row)
The only thing not shown clearly is the table creation statement which we can see in the corresponding .spec
file src/test/isolation/specs/insert-conflict-do-nothing-2.spec:
CREATE TABLE ints (key int, val text, PRIMARY KEY (key) INCLUDE (val));
From which we learn that INSERT INTO ON CONFLICT DO NOTHING
can also cause serialization failures.
Some of the tests also link to a paper: https://www.cs.umb.edu/~poneil/ROAnom.pdf A Read-Only Transaction Anomaly Under Snapshot Isolation. You know a software is serious when the bug reports are addressed by papers.