Ошибка сериализации что это

Сериализация — это автоматический процесс преобразования структур данных или состояний объектов в формат, который 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. Вызывайте их только в том случае, если нет альтернативы.


Offline

Gwalf

 


#1
Оставлено
:

3 апреля 2018 г. 15:25:38(UTC)

Gwalf

Статус: Активный участник

Группы: Участники

Зарегистрирован: 17.01.2018(UTC)
Сообщений: 38
Российская Федерация

Сказал(а) «Спасибо»: 1 раз

Нет доступа к ЦР.

Ping-CA выдал ошибку сериализации: CA error.jpg (75kb) загружен 49 раз(а).

С чем связана данная ошибка?
Как восстановить работу УЦ?


Вверх


Вверх
 


Offline

Захар Тихонов

 


#2
Оставлено
:

3 апреля 2018 г. 15:29:48(UTC)

Захар Тихонов

Статус: Сотрудник

Группы: Участники

Зарегистрирован: 17.08.2015(UTC)
Сообщений: 3,063
Мужчина
Тонга
Откуда: Калининград

Сказал «Спасибо»: 35 раз
Поблагодарили: 548 раз в 525 постах

Выполните команду на ЦР
Get-CAReference
и приложите вывод.

Также пришлите вывод команды с сервера ЦС
(Get-Item ‘CA:<имя ЦС>’).Subject


Вверх


Offline

Gwalf

 


#3
Оставлено
:

3 апреля 2018 г. 15:33:08(UTC)

Gwalf

Статус: Активный участник

Группы: Участники

Зарегистрирован: 17.01.2018(UTC)
Сообщений: 38
Российская Федерация

Сказал(а) «Спасибо»: 1 раз

ЦР и ЦС развернуты в единственном экземпляре на одной машине.
Сертификаты не просрочены.


Вверх


Offline

Захар Тихонов

 


#4
Оставлено
:

3 апреля 2018 г. 15:42:41(UTC)

Захар Тихонов

Статус: Сотрудник

Группы: Участники

Зарегистрирован: 17.08.2015(UTC)
Сообщений: 3,063
Мужчина
Тонга
Откуда: Калининград

Сказал «Спасибо»: 35 раз
Поблагодарили: 548 раз в 525 постах

Ок, еще раз:
Выполните команду на ЦР
Get-CAReference
и приложите вывод.

Также пришлите вывод команды с сервера ЦС
(Get-Item ‘CA:<имя ЦС>’).Subject

Если это один сервер, то это не отменяет того, что нужно выполнить команды и прислать выводы.


Вверх


Offline

Gwalf

 


#5
Оставлено
:

3 апреля 2018 г. 16:06:22(UTC)

Gwalf

Статус: Активный участник

Группы: Участники

Зарегистрирован: 17.01.2018(UTC)
Сообщений: 38
Российская Федерация

Сказал(а) «Спасибо»: 1 раз

Get-CAReference: Get-CAReference.jpg (122kb) загружен 40 раз(а).

Где можно проверить имя ЦС?

Отредактировано пользователем 3 апреля 2018 г. 16:17:07(UTC)
 | Причина: Не указана


Вверх


Offline

Захар Тихонов

 


#6
Оставлено
:

3 апреля 2018 г. 16:18:09(UTC)

Захар Тихонов

Статус: Сотрудник

Группы: Участники

Зарегистрирован: 17.08.2015(UTC)
Сообщений: 3,063
Мужчина
Тонга
Откуда: Калининград

Сказал «Спасибо»: 35 раз
Поблагодарили: 548 раз в 525 постах

Посмотрите его в Диспетчере УЦ.


Вверх


Offline

Gwalf

 


#7
Оставлено
:

3 апреля 2018 г. 16:39:44(UTC)

Gwalf

Статус: Активный участник

Группы: Участники

Зарегистрирован: 17.01.2018(UTC)
Сообщений: 38
Российская Федерация

Сказал(а) «Спасибо»: 1 раз

Subject.jpg (30kb) загружен 34 раз(а).


Вверх


Offline

Захар Тихонов

 


#8
Оставлено
:

3 апреля 2018 г. 16:43:10(UTC)

Захар Тихонов

Статус: Сотрудник

Группы: Участники

Зарегистрирован: 17.08.2015(UTC)
Сообщений: 3,063
Мужчина
Тонга
Откуда: Калининград

Сказал «Спасибо»: 35 раз
Поблагодарили: 548 раз в 525 постах

Возможно остановлена служба SQL. В Диспетчере конфигурации SQL, ваш экземпляр SQL запущен?


Вверх


Offline

Gwalf

 


#9
Оставлено
:

3 апреля 2018 г. 17:00:12(UTC)

Gwalf

Статус: Активный участник

Группы: Участники

Зарегистрирован: 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)


Вверх


Offline

Захар Тихонов

 


#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 код. Требует предварительной явной регистрации сериализуемых производных типов, чтобы иметь возможность сопоставлять имена контрактов данных с именами типов .NET
  • NetDataContractSerializer — характеризуется тесной привязкой типов .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 64
  • XmlAttribute и 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#
1
public void OnDeserialization(object sender)

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

Для задач, когда нужно перед записью в файл изменить значения некоторых значений (например зашифровать), существует другой интерфейс —

ISerializable и две его функции:

C#
1
2
3
public virtual void GetObjectData(SerializationInfo info,  StreamingContext context) {}
// The following constructor is for deserialization
protected ShoppingCartItem(SerializationInfo info,  StreamingContext context) {}

Вот маленький шаблонный пример как ими воспользоваться:

C#
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.IO;
 
namespace CustomSerialization
{
  class Program
  {
    [Serializable]
    public class SomeClass:ISerializable
    {
      [NonSerialized]
      public string secret1,secret2;
      public string open1,open2;
 
      public SomeClass(string secret1,string secret2,string open1,string open2)
      {
        this.secret1=secret1;
        this.secret2=secret2;
        this.open1=open1;
        this.open2=open2;
      }
 
      private string CryptF(string s)
      {
        //тут можно вызвать какой то метод шифрования
        //для простоты я возвращу туже строку
        return s;
      }
 
      private string DecryptF(string s)
      {
        //тут можно вызвать какой то метод дешифрования
        //для простоты я возвращу туже строку
        return s;
      }
 
      #region ISerializable Members
        private SomeClass(SerializationInfo info,StreamingContext context)
        {
          //считываем все поля, но те которые нужно, предварительно "расшифровываем"
          secret1=DecryptF(info.GetString("Some crypted secret1"));
          secret2=DecryptF(info.GetString("Some crypted secret2"));
          open1=info.GetString("public1");
          open2=info.GetString("public2");
        }
 
        public void GetObjectData(SerializationInfo info,StreamingContext context)
        {
          //добавлять нужно все поля, но те которые нужно, передаются в "зашифрованном" виде
          //в качестве идентификаторов можно использовать любой текст
          info.AddValue("Some crypted secret1",CryptF(secret1));
          info.AddValue("Some crypted secret2",CryptF(secret2));
          info.AddValue("public1",open1);
          info.AddValue("public2",open2);
        }
      #endregion
    }
 
    static void Main(string[] args)
    {
      SomeClass a=new SomeClass("top secret1","top secret2","some public text1",
        "some public text2");
      
      //serialization
      BinaryFormatter bf=new BinaryFormatter();
      FileStream fs=new FileStream("data.bin",FileMode.Create);
      bf.Serialize(fs,a);
      fs.Close();
 
      //deserialization
      fs=new FileStream("data.bin",FileMode.Open);
      SomeClass b=(SomeClass)bf.Deserialize(fs);
 
      Console.WriteLine("{0}; {1}; {2}; {3}",b.secret1,b.secret2,b.open1,b.open2);
      Console.ReadKey();
    }
  }
}

Для класса 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#
1
public void OnDeserialization(object sender)

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

Для задач, когда нужно перед записью в файл изменить значения некоторых значений (например зашифровать), существует другой интерфейс —

ISerializable и две его функции:

C#
1
2
3
public virtual void GetObjectData(SerializationInfo info,  StreamingContext context) {}
// The following constructor is for deserialization
protected ShoppingCartItem(SerializationInfo info,  StreamingContext context) {}

Вот маленький шаблонный пример как ими воспользоваться:

C#
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.IO;
 
namespace CustomSerialization
{
  class Program
  {
    [Serializable]
    public class SomeClass:ISerializable
    {
      [NonSerialized]
      public string secret1,secret2;
      public string open1,open2;
 
      public SomeClass(string secret1,string secret2,string open1,string open2)
      {
        this.secret1=secret1;
        this.secret2=secret2;
        this.open1=open1;
        this.open2=open2;
      }
 
      private string CryptF(string s)
      {
        //тут можно вызвать какой то метод шифрования
        //для простоты я возвращу туже строку
        return s;
      }
 
      private string DecryptF(string s)
      {
        //тут можно вызвать какой то метод дешифрования
        //для простоты я возвращу туже строку
        return s;
      }
 
      #region ISerializable Members
        private SomeClass(SerializationInfo info,StreamingContext context)
        {
          //считываем все поля, но те которые нужно, предварительно "расшифровываем"
          secret1=DecryptF(info.GetString("Some crypted secret1"));
          secret2=DecryptF(info.GetString("Some crypted secret2"));
          open1=info.GetString("public1");
          open2=info.GetString("public2");
        }
 
        public void GetObjectData(SerializationInfo info,StreamingContext context)
        {
          //добавлять нужно все поля, но те которые нужно, передаются в "зашифрованном" виде
          //в качестве идентификаторов можно использовать любой текст
          info.AddValue("Some crypted secret1",CryptF(secret1));
          info.AddValue("Some crypted secret2",CryptF(secret2));
          info.AddValue("public1",open1);
          info.AddValue("public2",open2);
        }
      #endregion
    }
 
    static void Main(string[] args)
    {
      SomeClass a=new SomeClass("top secret1","top secret2","some public text1",
        "some public text2");
      
      //serialization
      BinaryFormatter bf=new BinaryFormatter();
      FileStream fs=new FileStream("data.bin",FileMode.Create);
      bf.Serialize(fs,a);
      fs.Close();
 
      //deserialization
      fs=new FileStream("data.bin",FileMode.Open);
      SomeClass b=(SomeClass)bf.Deserialize(fs);
 
      Console.WriteLine("{0}; {1}; {2}; {3}",b.secret1,b.secret2,b.open1,b.open2);
      Console.ReadKey();
    }
  }
}

Для класса 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.

  • Ошибка сервисов google play please complete action required by сообщения как исправить
  • Ошибка сериализации файла настроек key collector
  • Ошибка сервисов google play please complete action required by google pay
  • Ошибка сериал франция сколько серий
  • Ошибка сервисов google play bluestacks