- •А.А. Волосевич
- •2. Базовые технологии платформы .Net 5
- •2. Базовые технологии платформы .Net 4
- •2. Базовые технологии платформы .Net
- •2.1. Работа с Числами
- •2.2. Дата и время
- •2.3. Работа со строками и текстом
- •2.4. Преобразование информации
- •2.5. Отношения равенства и порядка
- •Сравнение для выяснения равенства
- •Сравнение для выяснения порядка
- •2.6. Жизненный цикл объектов
- •Алгоритм «сборки мусора»
- •Финализаторы и интерфейс iDisposable
- •2.7. Перечислители и итераторы
- •2.8. Интерфейсы стандартных коллекций
- •2.9. Массивы и класс system.Array
- •2.10. Типы для работы с коллекциями-списками
- •2.11. Типы для работы с коллекциями-множествами
- •2.12. Типы для работы с коллекциями-словарями
- •2.13. Типы для создания пользовательских коллекций
- •2.14. Технология linq to objects
- •1. Оператор условия Where().
- •2. Операторы проекций.
- •3. Операторы упорядочивания.
- •4. Оператор группировки GroupBy().
- •5. Операторы соединения.
- •6. Операторы работы с множествами.
- •7. Операторы агрегирования.
- •8. Операторы генерирования.
- •9. Операторы кванторов и сравнения.
- •10. Операторы разбиения.
- •11. Операторы элемента.
- •12. Операторы преобразования.
- •2.15. Работа с объектами файЛовой системы
- •2.16. Ввод и вывод информации
- •Потоки данных и декораторы потоков
- •2. Классы для работы с потоками, связанными с хранилищами.
- •3. Декораторы потоков.
- •4. Адаптеры потоков.
- •Адаптеры потоков
- •2.17. Основы xml
- •2.18. Технология linq to xml
- •Создание, сохранение, загрузка xml
- •Запросы, модификация и трансформация xml
- •Пространства имен xml
- •2.19. ДОполнительные возможности обработки xml
- •2.20. Сериализация
- •Сериализация времени выполнения
- •Сериализация контрактов данных
- •2.21. Состав и взаимодействие сборок
- •2.22. Метаданные и получение информации о типах
- •2.23. Позднее связывание и кодогенерация
- •2.24. Динамические типы
- •2.25. Атрибуты
- •2.26. Файлы конфигуРации
- •2.27. Основы мНогопоточноГо программирования
- •2.28. Синхронизация потоков
- •2.29. Библиотека параллельных расширений
- •Параллелизм на уровне задач
- •Параллелизм при императивной обработке данных
- •Параллелизм при декларативной обработке данных
- •Обработка исключений и отмена выполнения задач
- •Коллекции, поддерживающие параллелизм
- •2.30. Асинхронный вызов методов
- •2.31. Процессы и домены
- •2.32. Безопасность
- •Разрешения на доступ
- •Изолированные хранилища
- •Криптография
- •2.33. Диагностика
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 года.