Контракт данных – это тип (класс или структура), описывающий информационный фрагмент. Если в качестве контракта данных используется обычный класс, информационный фрагмент образуют открытые поля и свойства. Можно пометить тип атрибутом [DataContract]. Тогда информационный фрагмент будут составлять поля и свойства, помеченные атрибутом [DataMember]1. Видимость элементов при этом роли не играет.
[DataContract]
public class Student
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
[DataMember]
public double GPA { get; set; }
}
Атрибут [DataContract] имеет свойства Name и Namespace для указания имени и пространства имён корневого XML-элемента. У атрибута [DataMember] есть свойство Name, а также свойства Order (порядок сериализации членов контракта), IsRequired (обязательный элемент в сериализованном потоке), EmitDefaultValue (запись в поток значений элемента по умолчанию).
Если контракт будет десериализоваться в объекты потомков своего типа, эти типы должны быть упомянуты при помощи атрибута [KnownType]2.
[DataContract]
[KnownType(typeof(Postgraduate))]
public class Student { . . . }
public class Postgraduate : Student { . . . }
Если контракт является коллекцией объектов (как класс Group), он маркируется атрибутом [CollectionDataContract]. Кроме этого, для методов контракта данных применимы атрибуты [OnSerializing], [OnSerialized], [OnDeserializing], [OnDeserialized].
Для сериализации контракта данных используются классы:
-
DataContractSerializer сериализует контракт;
-
NetDataContractSerializer сериализует данные и тип контракта;
-
DataContractJsonSerializer сериализует контракта в формате JSON.
Рассмотрим ряд примеров сериализации контрактов данных:
var student = new Student { Name = "Smirnov", Age = 18, GPA = 9 };
// конструктор DataContractSerializer требует типа контракта данных
var ds = new DataContractSerializer(typeof(Student));
// сериализация (по умолчанию используется формат XML)
using (Stream s = File.Create("studentDS.xml"))
{
ds.WriteObject(s, student);
}
// десериализация
using (Stream s = File.OpenRead("studentDS.xml"))
{
student = (Student)ds.ReadObject(s);
}
// сериализация и десериализация в двоичном формате
using (Stream bs = File.Create("studentDS.bin"))
{
using (XmlDictionaryWriter w = XmlDictionaryWriter.CreateBinaryWriter(bs))
{
ds.WriteObject(w, student);
}
}
using (Stream bs = File.OpenRead("studentDS.bin"))
{
using (XmlDictionaryReader r = XmlDictionaryReader.CreateBinaryReader(bs,
XmlDictionaryReaderQuotas.Max))
{
student = (Student)ds.ReadObject(r);
}
}
// сериализация в формате JSON
var jsonds = new DataContractJsonSerializer(typeof(Student));
using (Stream s = File.Create("student.json"))
{
jsonds.WriteObject(s, student);
}
// используя NetDataContractSerializer, тип указывать не нужно
var netds = new NetDataContractSerializer();
using (Stream s = File.Create("studentNDS.xml"))
{
netds.WriteObject(s, student);
}
XML-сериализация
Сериализацию в формате XML можно выполнить при помощи класса XmlSerializer из пространства имён System.Xml.Serialization. При таком подходе сохраняются public-элементы объекта. Кроме этого, тип объекта должен быть открытым и иметь public-конструктор без параметров.
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public double GPA { get; set; }
}
var student = new Student {Name = "Smirnov", Age = 18, GPA = 9};
// при создании XmlSerializer требуется указать сериализуемый тип
var serializer = new XmlSerializer(typeof(Student));
// сериализация
using (Stream stream = File.Create("student.xml"))
{
serializer.Serialize(stream, student);
}
// десериализация
using (Stream stream = File.OpenRead("student.xml"))
{
student = (Student) serializer.Deserialize(stream);
}
Настройка XML-сериализации может быть выполнена при помощи атрибутов. [XmlRoot] применяется к типу и задаёт корневой элемент в XML-файле. При помощи [XmlElement] настраивается имя и пространство имён XML-элемента. [XmlAttribute] используется, если член класса требуется сохранить в виде XML-атрибута. Поля и свойства, которые не должны сохраняться, помечаются атрибутом [XmlIgnore]. Если тип содержит коллекцию объектов, то настройка имени этой коллекции и имени отдельного элемента выполняется при помощи атрибутов [XmlArray] и [XmlArrayItem].
public class Student
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("age")]
public int Age { get; set; }
[XmlIgnore]
public double GPA { get; set; }
}
[XmlRoot("students")]
public class Group
{
public Student BestStudent { get; set; }
[XmlArray("list")]
[XmlArrayItem("student")]
public List List { get; set; }
}
Отметим, что XML-сериализация не может сохранить коллекции-словари. Если выполняется XML-сериализация объекта, реализующего интерфейс IEnumerable (обычный или универсальный), сохраняются только те элементы, которые доступны через перечислитель.
2.21. Состав и взаимодействие сборок
В платформе .NET сборка (assembly) – это единица развёртывания и контроля версий. Сборка состоит из одного или нескольких программных модулей и, возможно, данных ресурсов. Эти компоненты могут размещаться в отдельных файлах, либо содержаться в одном файле. В любом случае, сборка содержит в некотором из своих файлов манифест, описывающий состав сборки. Будем называть сборку однофайловой, если она состоит из одного файла. В противном случае сборку будем называть многофайловой. Тот файл, который содержит манифест сборки, будем называть главным файлом сборки.
Рис. 3. Однофайловая и многофайловая сборки.
Простые приложения обычно представлены однофайловыми сборками. При разработке сложных приложений переход к многофайловым сборкам даёт следующие преимущества:
-
Ресурсы (текстовые строки, изображения и т. д.) можно хранить вне приложения, что позволяет при необходимости изменять ресурсы без перекомпиляции приложения.
-
Если исполняемый код приложения разделён на несколько модулей, то модули загружаются в память только по мере надобности.
-
Скомпилированный модуль может использоваться в нескольких сборках.
Рассмотрим пример создания и использования многофайловых сборок1. Пусть требуется построить консольное приложение, в котором функция Main() печатает на экране строку. Предположим, что эту строку возвращает метод GetText() класса TextClass.
public static class TextClass
{
public static string GetText()
{
return "message";
}
}
Файл TextClass.cs с исходным кодом класса TextClass скомпилируем в виде модуля (обратите внимание на ключ компилятора):
csc.exe /t:module TextClass.cs
После компиляции получим файл-модуль TextClass.netmodule. Далее, создадим консольное приложение (файл MainClass.cs):
using System;
public class MainClass
{
public static void Main()
{
Console.WriteLine("Text from resource");
Console.WriteLine(TextClass.GetText());
}
}
Теперь соберём многофайловую сборку. Ключ компилятора /addmodule позволяет добавить к сборке ссылку на внешний модуль. Он должен применяться для каждого подключаемого модуля.
csc.exe /addmodule:textclass.netmodule MainClass.cs
В итоге получим многофайловую сборку, состоящую из двух файлов: главного файла mainclass.exe и файла-модуля textclass.netmodule. Мы можем создать новую сборку, в которой используется код из модуля textclass.netmodule, то есть сделать этот модуль разделяемым между несколькими сборками. Важное замечание: предполагается, что все файлы, составляющие нашу многофайловую сборку, размещены в одном каталоге.
Рассмотрим вопрос взаимодействия сборок. Как правило, крупные программные проекты состоят из нескольких сборок, связанных ссылками. Среди этих сборок имеется некая основная (обычно оформленная как исполняемый файл *.exe), а другие сборки играют роль подключаемых библиотек с кодом необходимых типов (обычно такие сборки – это файлы с расширением *.dll).
Представим пример, который будет использоваться в дальнейшем. Пусть имеется класс (в файле UL.cs), содержащий «полезную» функцию:
namespace UsefulLibrary
{
public class UsefulClass
{
public void Print()
{
System.Console.WriteLine("Useful function");
}
}
}
Скомпилируем данный класс как библиотеку типов (расширение *.dll):
csc.exe /t:library UL.cs
Пусть основное приложение (файл main.cs) собирается использовать код из сборки UL.dll:
using System;
using UsefulLibrary;
public class MainClass
{
public static void Main()
{
// используем класс из другой сборки
UsefulClass a = new UsefulClass();
a.Print();
}
}
Ключ компилятора /r (или /reference) позволяет установить ссылку на требуемую сборку, указав путь к ней. Скомпилируем приложение main.cs:
csc.exe /r:UL.dll main.cs
Платформа .NET разделяет сборки на локальные (или сборки со слабыми именами) и глобальные (или сборки с сильными именами). Если UL.dll рассматривается как локальная сборка, то при выполнении приложения она должна находиться в том же каталоге, что и main.exe1. Локальные сборки обеспечивают простоту развёртывания приложения (все его компоненты сосредоточены в одном месте) и изолированность компонентов. Имя локальной сборки – слабое имя – это имя файла сборки без расширения.
Хотя использование локальных сборок имеет свои преимущества, иногда необходимо сделать сборку общедоступной. До появления платформы .NET доминировал подход, при котором код общих библиотек помещался в системный каталог простым копированием фалов при установке. Такой подход привел к проблеме, известной как «ад DLL» (DLL Hell). Инсталлируемое приложение могло заменить общую библиотеку новой версией, при этом другие приложения, ориентированные на старую версию библиотеки, переставали работать. Для устранения «ада DLL» в платформе .NET используется специальное защищенное хранилище сборок (Global Assembly Cache, GAC).
Сборки, помещаемые в GAC, должны удовлетворять определённым условиям. Во-первых, такие глобальные сборки должны иметь цифровую подпись. Это исключает подмену сборок злоумышленниками. Во-вторых, для глобальных сборок отслеживаются версии и языковые культуры. Допустимой является ситуация, когда в GAC находятся разные версии одной и той же сборки, используемые разными приложениями.
Сборка, помещенная в GAC, получает сильное имя. Как раз использование сильного имени является тем признаком, по которому среда исполнения понимает, что речь идет не о локальной сборке, а о сборке из GAC. Сильное имя включает: имя главного файла сборки (без расширения), версию сборки, указание о региональной принадлежности сборки и маркер открытого ключа сборки:
Name, Version=1.2.0.0, Culture=neutral, PublicKeyToken=1234567812345678
Рассмотрим процесс создания сборки с сильным именем на примере сборки UL.dll. Первое: необходимо создать пару криптографических ключей для цифровой подписи сборки. Для этих целей служит утилита sn.exe, входящая в состав Microsoft .NET Framework SDK.
sn.exe -k keys.snk
Параметр -k указывает на создание ключей, keys.snk – это файл с ключами. Просмотреть полученные ключи можно, используя команду sn.exe -tp.
Далее необходимо подписать сборку полученными ключами. Для этого используется специальный атрибут1 уровня сборки [AssemblyKeyFile] или ключ компилятора командной строки /keyfile:
using System;
using System.Reflection;
[assembly: AssemblyKeyFile("keys.snk")]
namespace UsefulLibrary { . . . }
Обратите внимание: для использования атрибута необходимо подключить пространство имен System.Reflection, а в качестве параметра атрибута указывается полное имя файла с ключами.
После подписания сборку можно поместить в GAC. Простейший вариант сделать это – использовать утилиту gacutil.exe, входящую в состав .NET Framework SDK. При использовании ключа /i сборка помещается в GAC, а ключ /u удаляет сборку из GAC:
gacutil.exe /i ul.dll
Теперь сборка UL.dll помещена в GAC. Ее сильное имя (для ссылки в программах) имеет вид:
UL, Version=0.0.0.0, Culture=neutral, PublicKeyToken= ff824814c57facfe
Компонентом сильного имени является версия сборки. Если программист желает указать версию, то для этого используется атрибут [AssemblyVersion]. Номер версии имеет формат Major.Minor.Build.Revision. Часть Major является обязательной. Любая другая часть может быть опущена (в этом случае она полагается равной нулю). Часть Revision можно задать как *, тогда компилятор генерирует её как количество секунд, прошедших с полуночи, деленное на два. Часть Build также можно задать как *. Тогда для неё будет использовано количество дней, прошедших с 1 февраля 2000 года.
Поделитесь с Вашими друзьями: |