суррогаты сериализации
До этого момента мы обсуждали способы изменения реализации типов, позволяю-
щие управлять сериализацией и десериализацией их экземпляров. Однако суще-
ствует возможность переопределить поведение этих процессов при помощи кода,
не принадлежащего реализации типа. Для чего это может быть нужно?
Разработчик может сериализовать типы, для которых возможность сериализации
не была учтена при исходном проектировании.
Возможность отображения между разными версиями одного типа.
Чтобы этот механизм заработал, нужно определить «суррогатный тип», который
возьмет на себя работу по сериализации и десериализации существующего типа.
Затем следует зарегистрировать экземпляр суррогатного типа, сообщив модулю
форматирования, за действия какого существующего типа он будет отвечать. В ре-
зультате при попытке сериализовать или десериализовать экземпляр существующего
типа модуль форматирования будет вызывать методы, определенные суррогатным
объектом. Следующий пример показывает, как все это работает.
Тип суррогата сериализации должен реализовывать интерфейс
System.Runtime.
Serialization.ISerializationSurrogate
, определяемый в FCL следующим об-
разом:
public interface ISerializationSurrogate {
void GetObjectData(Object obj, SerializationInfo info,
StreamingContext context);
Object SetObjectData(Object obj, SerializationInfo info,
StreamingContext context, ISurrogateSelector selector);
}
692
Глава.24 .Сериализация
Теперь посмотрим на пример использования этого интерфейса. Предположим,
программа содержит объекты
DateTime
, значения которых привязаны к компьютеру
пользователя. Каким образом сериализовать эти объекты в поток ввода-вывода,
указав их значения, как всемирное время? Ведь только при таком подходе вы мо-
жете перенаправить поток на машину, расположенную на другом конце планеты,
сохранив корректные значения даты и времени. Так как вы не можете менять тип
DateTime
, представленный в FCL, остается определить собственный суррогатный
класс, управляющий сериализацией и десериализацией объектов
DateTime
. Вот
как он выглядит:
internal sealed class UniversalToLocalTimeSerializationSurrogate :
ISerializationSurrogate {
public void GetObjectData(
Object obj, SerializationInfo info, StreamingContext context) {
// Переход от локального к мировому времени
info.AddValue("Date", ((DateTime)obj).ToUniversalTime().ToString("u"));
}
public Object SetObjectData(Object obj, SerializationInfo info,
StreamingContext context, ISurrogateSelector selector) {
// Переход от мирового времени к локальному
return DateTime.ParseExact(
info.GetString("Date"), "u", null).ToLocalTime();
}
}
Здесь метод
GetObjectData
работает почти как одноименный метод интерфейса
ISerializable
. Отличается он всего одним параметром: ссылкой на «реальный» объ-
ект, который требуется сериализовать. В показанном варианте метода
GetObjectData
данный объект приводится к типу
DateTime
, его значение преобразуется из локаль-
ного в универсальное время, а полученная в итоге строка (отформатированная
с использованием универсального шаблона полной даты/времени) добавляется
в коллекцию
SerializationInfo
.
Для десериализации объекта
DateTime
вызывается метод
SetObjectData
. Ему
передается ссылка на объект
SerializationInfo
. Метод извлекает из коллекции
строковые данные, разбирает их как строку в формате универсальной полной
даты/времени и преобразует полученный объект
DateTime
в формат локального
машинного времени.
Первый параметр метода
SetObjectData
, объект
Object
, выглядит немного стран-
но. Непосредственно перед вызовом метода модуль форматирования выделяет место
(через статический метод
GetUninitializedObject
класса
FormatterServices
) под
экземпляр типа, для которого предназначается суррогат. Все поля этого экземпляра
имеют значение 0 или
null
, и для объекта не вызывается никаких конструкторов.
Метод
SetObjectData
может просто инициализировать его поля, используя значения
из переданного методу объекта
SerializationInfo
, а затем вернуть значение
null
.
В качестве альтернативы метод
SetObjectData
может создать совсем другой объект
693
Суррогаты.сериализации
или даже другой объектный тип и вернуть ссылку на него. В этом случае модуль
форматирования проигнорирует любые изменения, которые могли произойти
с объектом, переданным им в метод
SetObjectData
.
В моем примере класс
UniversalToLocalTimeSerializationSurrogate
действует
как суррогат для значимого типа
DateTime
. И поэтому параметр
obj
ссылается на
упакованный экземпляр типа
DateTime
. Менять поля в большинстве значимых
типов нельзя (так как они предполагаются неизменными), поэтому мой метод
SetObjectData
игнорирует параметр
obj
и возвращает новый объект
DateTime
с нужным значением.
Как же при сериализации/десериализации объекта
DateTime
модуль форматиро-
вания узнает о необходимости использования типа
ISerializationSurrogate
? Сле-
дующий код тестирует класс
UniversalToLocalTimeSerializationSurrogate
:
private static void SerializationSurrogateDemo() {
using (var stream = new MemoryStream()) {
// 1. Создание желаемого модуля форматирования
IFormatter formatter = new SoapFormatter();
// 2. Создание объекта SurrogateSelector
SurrogateSelector ss = new SurrogateSelector();
// 3. Селектор выбирает наш суррогат для объекта DateTime
ss.AddSurrogate(typeof(DateTime), formatter.Context,
new UniversalToLocalTimeSerializationSurrogate());
// ПРИМЕЧАНИЕ. AddSurrogate можно вызывать более одного раза
// для регистрации нескольких суррогатов
// 4. Модуль форматирования использует наш селектор
formatter.SurrogateSelector = ss;
// Создание объекта DateTime с локальным временем машины
// и его сериализация
DateTime localTimeBeforeSerialize = DateTime.Now;
formatter.Serialize(stream, localTimeBeforeSerialize);
// Поток выводит универсальное время в виде строки,
// проверяя, что все работает
stream.Position = 0;
Console.WriteLine(new StreamReader(stream).ReadToEnd());
// Десериализация универсального времени и преобразование
// объекта DateTime в локальное время
stream.Position = 0;
DateTime localTimeAfterDeserialize =
(DateTime)formatter.Deserialize(stream);
// Проверка корректности работы
Console.WriteLine(
"LocalTimeBeforeSerialize ={0}", localTimeBeforeSerialize);
продолжение
694
Do'stlaringiz bilan baham: |