Общие сведения о сериализации конструктора
Благодаря сериализации конструктора состояние компонентов может быть сохранено во время разработки или выполнения.
Сериализация объектов
.NET Framework поддерживает несколько типов сериализации, например, создание кода, двоичную сериализацию, сериализацию SOAP и XML-сериализацию.
Сериализация конструктора — это особая форма сериализации, в которой применяется способ постоянного хранения объектов, обычно используемый в средствах разработки.Сериализация конструктора — это процесс преобразования графа объекта в исходный файл, который впоследствии можно будет использовать для восстановления графа объекта.Исходный файл может содержать код, разметку или даже информацию из таблицы SQL.Сериализация конструктора поддерживается для всех объектов среды CLR.
Сериализация конструктора имеет несколько отличий от обычной сериализации объектов:
Объект, выполняющий сериализацию, отделен от объекта времени выполнения, поэтому логика режима конструирования может быть выведена из компонента.
Схема сериализации создавалась в предположении, что объект создается полностью инициализированным, а затем изменяется с помощью обращений к свойствам и методам в процессе десериализации.
Свойства объекта, значения которых для этого объекта не были заданы, не сериализуются.Наоборот, поток десериализации может инициализировать не все свойства.Более подробное описание правил сериализации см. ниже в подразделе "Основные правила сериализации".
Акцент делается на качестве содержимого потока сериализации, а не на полноте сериализации объекта.Если способ сериализации не определен, то объект, скорее всего, будет пропущен, но исключение при этом создано не будет.Сериализация конструктора обеспечивает ряд способов сериализации объекта в более простой и удобочитаемой форме по сравнению с непрозрачным большим двоичным объектом.
Поток сериализации может содержать больше данных, чем необходимо для десериализации.Например, сериализация исходного кода содержит код пользователя, смешанный с кодом, необходимым для десериализации объектного графа.Этот код пользователя необходимо сохранить в процессе сериализации и передать в процессе десериализации.
Примечание |
---|
Сериализация конструктора может быть использована как во время выполнения, так и во время конструирования. |
В приведенной ниже таблице показано, как обе эти задачи разработки реализуются с помощью инфраструктуры сериализации конструктора в .NET Framework.
Задача разработки |
Описание |
---|---|
Модульность |
Процесс сериализации может быть расширен для поддержки новых типов данных, обеспечивающих осмысленное и удобочитаемое описание. |
Простота расширения |
Процесс сериализации может быть с легкостью расширен для поддержки новых типов данных. |
Независимость от формата |
Объекты могут использоваться в файлах многих различающихся форматов; сериализация конструктора не привязана к какому-либо конкретному формату данных. |
Архитектура
Архитектура сериализации конструктора основана на метаданных, сериализаторах и диспетчере сериализации.В следующей таблице описываются функции всех участников архитектуры.
Участник |
Описание |
---|---|
Атрибуты метаданных |
Атрибут используется, чтобы связать тип T с некоторым сериализатором S.Помимо этого, архитектура поддерживает атрибут начальной загрузки, который может использоваться для установки объекта, обеспечивающего сериализаторы для тех типов, у которых их нет. |
Сериализаторы |
Сериализатор – это объект, который может сериализовать конкретный тип или набор типов.Для каждого формата данных существует базовый класс.Например, может существовать базовый класс DemoXmlSerializer, преобразующий объект в XML.Архитектура не зависит от конкретного формата сериализации, и включает реализацию данной архитектуры, построенную на основе объектной модели документов кода (CodeDOM). |
Диспетчер сериализации. |
Диспетчер сериализации — это объект, который предоставляет хранилище информации по всем сериализаторам, используемым для сериализации объектного графа.Граф, состоящий из 50 объектов, может иметь 50 различных сериализаторов, генерирующих собственные выходные данные.Диспетчер сериализации используется этими сериализаторами для взаимодействия друг с другом. |
Следующая иллюстрация и процедура демонстрируют, как могут быть сериализованы объекты в графе (в данном случае это объекты А и В).
Сериализация объектов в графе
Вызывающий объект запрашивает сериализатор для объекта А у диспетчера сериализации:
MySerializer s = manager.GetSerializer(a);
Атрибут метаданных типа A связывает объект A с сериализатором запрашиваемого типа.Вызывающий объект подает сериализатору запрос на сериализацию объекта А:
Blob b = s.Serialize(manager, a);
Сериализатор объекта A сериализует A.Для всех объектов, которые он встречает в ходе сериализации объекта А, он запрашивает у диспетчера сериализации дополнительный сериализатор:
MySerializer s2 = manager.GetSerializer(b); Blob b2 = s2.Serialize(manager, b);
Результат сериализации возвращается вызывающему объекту:
Blob b = ...
Основные правила сериализации
Компонент обычно обладает целым рядом свойств.Например, элемент управления Windows Forms Button имеет такие свойства, как BackColor, ForeColor и BackgroundImage.При размещении элемента управления Button в форме в конструкторе при просмотре созданного кода можно будет видеть, что в коде сохраняется только поднабор свойств.Обычно это те свойства, значения для которых были заданы в явном виде.
Порядок сериализации определяет сериализатор CodeDomSerializer, связанный с элементом управления Button.В следующем списке описываются некоторые правила, используемые сериализатором CodeDomSerializerпри сериализации значения свойства:
Если к свойству присоединен атрибут DesignerSerializationVisibilityAttribute, то сериализатор использует его, чтобы определить, подлежит ли свойство сериализации (например, с помощью значений Visible или Hidden), и определить порядок сериализации (Content).
Значения Visible или Hidden указывают на то, что свойство сериализуется.Значение Content задает способ сериализации свойства.
Если компонент содержит свойство DemoProperty и реализует метод ShouldSerializeDemoProperty, то среда разработки выполняет вызов этого метода с поздним связыванием, чтобы определить, необходима ли сериализация.Например, метод для свойства BackColor будет называться ShouldSerializeBackColor.
Если для свойства указан атрибут DefaultValueAttribute, значение по умолчанию сравнивается с текущим значением в компоненте.Свойство сериализуется только в том случае, если текущее значение не является значением по умолчанию.
Связанный с объектом конструктор также может использоваться при принятии решения о сериализации с помощью затенения свойств или самостоятельной реализации методов ShouldSerialize.
Примечание |
---|
Сериализатор перекладывает ответственность за принятие решения о сериализации на описатель PropertyDescriptor, связанный со свойством; в свою очередь, PropertyDescriptor использует перечисленные ранее правила. |
Если необходимо сериализовать компонент другим способом, можно создать собственный класс сериализатора, производный от класса CodeDomSerializer, и связать его с компонентом, используя атрибут DesignerSerializerAttribute.
Реализация интеллектуального сериализатора
Одним из требований при разработке сериализатора в случае необходимости добавления нового формата сериализации является обязательное обновление всех типов данных с помощью атрибута метаданных, обеспечивающего поддержку этого формата.Тем не менее, это требование может быть выполнено путем использования поставщиков сериализации и сериализаторов, использующих универсальные объектные метаданные.В этом подразделе описан предпочтительный способ реализации поддержки определенного формата в сериализаторе, благодаря которому практически устраняется потребность в создании множества пользовательских сериализаторов.
В следующей схеме определяется гипотетический формат XML, в котором будет сохраняться объектный граф.
<TypeName>
<PropertyName>
ValueString
</PropertyName>
</TypeName>
Данный формат сериализуется с использованием нового класса DemoXmlSerializer.
public abstract class DemoXmlSerializer
{
public abstract string Serialize(
IDesignerSerializationManager m,
object graph);
}
Важно понимать, что DemoXmlSerializer является модульным классом, создающим строку из фрагментов.Например, DemoXmlSerializer для типа данных Int32 вернет строку "23" при передаче целого значения 23.
Поставщики сериализации
По приведенному ранее примеру схемы ясно, что существует два фундаментальных типа, которые необходимо обрабатывать:
Объекты, имеющие дочерние свойства.
Объекты, которые могут быть преобразованы в текст.
Слишком сложно создавать для каждого класса отдельный сериализатор, способный преобразовать этот класс в текст или теги XML.Поставщики сериализации позволяют решить эту проблему, предоставляя механизм обратных вызовов, в котором объект может предоставить сериализатор для определенного типа.В этом примере набор доступных типов ограничен следующими условиями:
Если тип может быть преобразован в строку с помощью интерфейса IConvertible, будет использован класс StringXmlSerializer.
Если тип не может быть преобразован в строку, но является открытым и имеет пустой конструктор, будет использован класс ObjectXmlSerializer.
Если эти условия не выполняются, поставщик сериализации вернет значение null, свидетельствующее об отсутствии сериализаторов для объекта.
В следующем примере кода показано, как вызывающий сериализатор разрешает возникшую ошибку подобного рода.
internal class XmlSerializationProvider : IDesignerSerializationProvider
{
object GetSerializer(
IDesignerSerializationManager manager,
object currentSerializer,
Type objectType,
Type serializerType)
{
// Null values will be given a null type by this serializer.
// This test handles this case.
if (objectType == null)
{
return StringXmlSerializer.Instance;
}
if (typeof(IConvertible).IsSubclassOf(objectType))
{
return StringXmlSerializer.Instance;
}
if (objectType.GetConstructor(new object[]) != null)
{
return ObjectXmlSerializer.Instance;
}
return null;
}
}
Когда поставщик сериализации определен, он должен быть использован.Поставщик сериализации может быть передан диспетчеру сериализации с помощью метода AddSerializationProvider, но для этого необходим соответствующий вызов диспетчера сериализации.Поставщик сериализации может быть автоматически добавлен в диспетчер сериализации путем добавления атрибута DefaultSerializationProviderAttribute к сериализатору.Для этого необходимо, чтобы поставщик сериализации имел открытый пустой конструктор.В следующем примере кода показаны изменения, которые в этом случае потребуется внести в класс DemoXmlSerializer.
[DefaultSerializationProvider(typeof(XmlSerializationProvider))]
public abstract class DemoXmlSerializer
{
}
Теперь всякий раз, когда диспетчер сериализации получает запрос на какой-либо тип DemoXmlSerializer, в диспетчер сериализации будет добавляться поставщик сериализации по умолчанию, если он еще не был добавлен.
Сериализаторы
Приведенный в примере класс DemoXmlSerializer имеет два конкретных класса-сериализатора: StringXmlSerializer и ObjectXmlSerializer.В следующем примере кода показана реализация StringXmlSerializer.
internal class StringXmlSerializer : DemoXmlSerializer
{
internal StringXmlSerializer Instance = new StringXmlSerializer();
public override string Serialize(
IDesignerSerializationManager m,
object graph)
{
if (graph == null) return string.Empty;
IConvertible c = graph as IConvertible;
if (c == null)
{
// Rather than throwing exceptions, add a list of errors
// to the serialization manager.
m.ReportError("Object is not IConvertible");
return null;
}
return c.ToString(CultureInfo.InvariantCulture);
}
}
Реализация ObjectXmlSerializer несколько сложнее, поскольку в ней требуется выполнять перечисление открытых свойств объекта.В следующем примере кода показана реализация ObjectXmlSerializer.
internal class ObjectXmlSerializer : DemoXmlSerializer
{
internal ObjectXmlSerializer Instance = new ObjectXmlSerializer();
public override string Serialize(
IDesignerSerializationManager m,
object graph)
{
StringBuilder xml = new StringBuilder();
xml.Append("<");
xml.Append(graph.GetType().FullName);
xml.Append(">");
// Now, walk all the properties of the object.
PropertyDescriptorCollection properties;
Property p;
properties = TypeDescriptor.GetProperties(graph);
foreach(p in properties)
{
if (!p.ShouldSerializeValue(graph))
{
continue;
}
object value = p.GetValue(graph);
Type valueType = null;
if (value != null) valueType = value.GetType();
// Get the serializer for this property
DemoXmlSerializer s = m.GetSerializer(
valueType,
typeof(DemoXmlSerializer)) as DemoXmlSerializer;
if (s == null)
{
// Because there is no serializer,
// this property must be passed over.
// Tell the serialization manager
// of the error.
m.ReportError(string.Format(
"Property {0} does not support XML serialization",
p.Name));
continue;
}
// You have a valid property to write.
xml.Append("<");
xml.Append(p.Name);
xml.Append(">");
xml.Append(s.Serialize(m, value);
xml.Append("</");
xml.Append(p.Name);
xml.Append(">");
}
xml.Append("</");
xml.Append(graph.GetType().FullName);
xml.Append(">");
return xml.ToString();
}
}
Сериализатор ObjectXmlSerializer вызывает другие сериализаторы для значения каждого свойства.Такой подход обеспечивает два преимущества.Во-первых, ObjectXmlSerializer становится проще.Во-вторых, предоставляется точка расширения для типов, создаваемых независимыми разработчиками.Если ObjectXmlSerializer получает тип, который не может быть сохранен ни одним из этих сериализаторов, то для данного типа может быть использован пользовательский сериализатор.
Использование
Для использования новых сериализаторов необходимо создать экземпляр диспетчера IDesignerSerializationManager.У данного экземпляра можно запросить сериализатор, а затем запросить у этого сериализатора сериализацию объектов.В следующем примере кода в качестве сериализуемого объекта будет использован Rectangle, поскольку этот тип имеет пустой конструктор и четыре свойства, которые поддерживают интерфейс IConvertible.Вместо самостоятельной реализации IDesignerSerializationManager можно прибегнуть к реализации, предоставляемой диспетчером DesignerSerializationManager.
Rectangle r = new Rectangle(5, 10, 15, 20);
DesignerSerializationManager m = new DesignerSerializationManager();
DemoXmlSerializer x = (DemoXmlSerializer)m.GetSerializer(
r.GetType(), typeof(DemoXmlSerializer);
string xml = x.Serialize(m, r);
В результате будет создан следующий XML-код.
<System.Drawing.Rectangle>
<X>
5
</X>
<Y>
10
</Y>
<Width>
15
</Width>
<Height>
15
</Height>
</System.Drawing.Rectangle>
Примечание |
---|
XML-код не содержит отступов.Это легко исправить с помощью свойства Context диспетчера IDesignerSerializationManager.Каждый уровень сериализатора может добавлять в контекстный стек объект, содержащий текущий уровень отступа; все сериализаторы могут искать эти объекты в стеке и использовать их для обеспечения отступов. |
См. также
Ссылки
DefaultSerializationProviderAttribute