Оптимизация сериализации в .NET - Принадлежащие данные

ОГЛАВЛЕНИЕ

Принадлежащие данные

Раньше упоминалась концепция 'принадлежащих данных', теперь определим ее: принадлежащие данные - это данные объекта, которые:

  • Любой тип значения/структура данных, такая как Int32 или Byte или Boolean и т.д. Так как типы значения являются структурами и восстанавливаются, когда они передаются по кругу, на данные любого типа внутри вашего объекта не может повлиять другой объект, и поэтому сериализовать/десериализовать их всегда безопасно.
  • Строки. Хотя они имеют ссылочный тип, они неизменяемы (их нельзя изменить после создания), поэтому имеют семантику типа значения, и сериализовать их всегда безопасно.
  • Другие ссылочные типы, созданные или переданные в ваш объект, которые никогда не используются внешними объектами. Они могут включать внутренние или приватные Hashtable, ArrayList, Array и т.д., потому что они не доступны для внешних объектов. Сюда могут входить объекты, которые были созданы как внешние и переданы для использования исключительно вашему объекту.
  • Другие ссылочные типы (снова созданные вашим объектом или переданные ему), которые могут использоваться другими объектами, но вы знаете, что это не вызовет проблемы во время десериализации. Проблема здесь в том, что ваш объект сам не знает о том, что другие объекты могут быть сериализованы в том же самом графе объекта. Поэтому если сериализуется коллективно используемый объект в byte[] путем использования  SerializationWriter, и тот же самый коллективно используемый объект был сериализован другим внешним объектом с помощью использования его SerializationWriter, то два экземпляра могут оказаться десериализованными – другой экземпляр для каждого, – потому что инфраструктура сериализации никогда не сможет обнаружить и обработать многократные ссылки.

Это может быть или не быть проблемой, в зависимости от коллективно используемого объекта. Если коллективно используемый объект был неизменяемым/не имел эксплуатационных данных, то наличие двух экземпляров, созданных во время десериализации, хотя и неэффективное, не вызовет проблемы. Но если присутствовали эксплуатационные данные, и они должны были коллективно использоваться двумя объектами, ссылающимися на них, то это может быть проблемой, потому что каждый теперь имеет свою собственную независимую копию. Наихудший сценарий – когда совместно используемый объект хранит обратные ссылки на объект, который ссылается на него, то есть риск появления цикла, который быстро вызовет ошибку сериализации с исключением OutOfMemoryException или StackOverflow. Это является проблемой, только если объекты с многократными ссылками сериализуются в один и тот же граф. Если один объект является частью графа, то объект, на который он ссылается, можно рассматривать как 'принадлежащие данные' – другие ссылки станут несущественными, – но определение и разрешение данной ситуации является вашей задачей.

Нижняя строка нужна, чтобы убедиться, что только тщательно идентифицированные ' принадлежащие данные' сохраняются внутри SerializationWriter, и позволить инфраструктуре сериализации .NET выполнить остальную работу.

Как это работает (вкратце)

SerializationWriter имеет определенное число перегруженных методов Write(xxx) для определенного числа типов. Он также имеет набор методов WriteOptimized(xxx), которые могут сохранить определенные типы более оптимальным образом, но могут иметь некоторые ограничения на сохраняемые значения (которые документированы для метода). Для данных, которые неизвестны во время компиляции, есть метод WriteObject(object), который сохранит тип данных, так же как и значение, так что SerializationReader знает, как снова восстановить данные. Тип данных сохраняется с помощью использования одиночного байта, который основан на внутреннем перечислении, называемом SerializedType.

SerializationReader имеет определенное число методов, соответствующих своим аналогам в SerializationWriter. Они не могут быть перегружены таким же образом, поэтому каждый является отдельным методом, с именем, описывающим его использование. Например, строка, записанная с помощью Write(string), будет извлечена с помощью ReadString(), WriteOptimized(Int32) будет извлечен с помощью ReadOptimizedInt32(), и WriteObject (object) будет извлечен с помощью ReadObject(), и так далее. Пока эквивалентный метод для извлечения данных вызывается для SerializationReader и, что важно, в таком же порядке, то вы получите назад точно такие те же данные, которые были записаны.