Главная страница   /   15.2. Управление жизненным циклом (Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Марк Симан

15.2. Управление жизненным циклом

В главе 8 обсуждался процесс управления жизненным циклом, в том числе наиболее универсальные стили существования, к примеру, Singleton и Transient. Сделать обзор доступных стилей существования MEF довольно легко, поскольку MEF обладает только двумя стилями существования, продемонстрированными в таблице 15-3.

Таблица 15-3: Стили существования MEF
Название Комментарии
Shared Этот стиль считается стилем по умолчанию, несмотря на то, что он зависит от соответствия импортируемых и экспортируемых элементов. Так в MEF называется стиль существования Singleton.
NonShared Так в MEF называется стиль существования Transient. Контейнер отслеживает экземпляры.

Примечание

В MEF стили существования называются политиками создания.

Реализации в MEF стилей существования Transient и Singleton эквивалентны стилям существования, описанным в главе 8, поэтому в этой главе я не буду тратить время на их рассмотрение.

Подсказка

В MEF используемым по умолчанию является стиль Singleton. Этим он отличается от других DI-контейнеров. Как уже обсуждалось в главе 8, Singleton – самый эффективный, но не всегда самый безопасный стиль. Поэтому в MEF эффективность приоритетнее безопасности.

В MEF существует только две политики создания, а реализовать пользовательские жизненные циклы в нем невозможно, поэтому по сравнению с остальными главами части 4 этот раздел довольно краток. Вы увидите, как объявлять стили существования для частей и как высвобождать компоненты. После прочтения этого раздела вы сможете создавать политики создания в своем собственном приложении.

Сообразно остальному API контейнера MEF политика создания определяется с помощью атрибутов.

Объявление политики создания

Объявить политику создания можно посредством добавления в класс атрибута [PartCreationPolicy]:

[Export(typeof(IIngredient))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class SauceBéarnaise : IIngredient { }

Для атрибута [PartCreationPolicy] необходимо, чтобы вы указали значение для CreationPolicy. В этом примере в качестве значения CreationPolicy вы указали "NonShared" для того, чтобы объявить SauceBéarnaise как Transient-компонент. Но как демонстрирует таблица 15-4, у перечисления CreationPolicy есть еще несколько вариантов.

Таблица 15-4: Значение CreationPolicy
Значение Описание
Any Это значение используется по умолчанию. Часть может быть как Singleton-компонентом, так и Transient. Но пока явно не будет запрашиваться значение NonShared, часть будет вести себя как Shared.
Shared Часть является Singleton-компонентом.
NonShared Часть всегда выступает в роли Transient –компонента.

Примечание

Атрибут [PartCreationPolicy] можно применять только к классам. Он отличается от атрибутов [Import] и [Export], которые можно применять к классам, членам классов и параметрам.

Неудивительно, что мы можем использовать значения Shared и NonShared, но вот то, что мы можем указать значение Any, возможно, слегка вас удивило. Мы вкратце рассмотрим значение Any, но сначала завершим рассмотрение значений Shared и NonShared.

Экспорт с помощью политик создания

Как мы только что обсуждали, политику создания мы задаем с помощью атрибута [PartCreationPolicy]. Если мы не указываем значение этого атрибута, то используется значение Any. Однако если мы поставим этот атрибут рядом с атрибутами [Import] и [ImportingConstructor], которые вы уже видели в этой главе, то по умолчанию будет использоваться стиль Singleton.

В этом контексте два приведенных ниже примера эквивалентны:

[Export(typeof(IIngredient))]
public class SauceBéarnaise : IIngredient { }
[Export(typeof(IIngredient))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class SauceBéarnaise : IIngredient { }

Единственное отличие между двумя верхними строчками кода и тремя нижними заключается в том, что в нижнем примере вы явно указываете на то, что часть является Singleton-компонентом. До тех пор, пока импортер не запрашивает конкретную политику создания, поведение приведенных примеров кода будет идентично.

Разница состоит в том, что в верхнем примере не указана явная политика создания, т.е. используется значение Any. Несмотря на то, что в большинстве случаев по умолчанию используется поведение, соответствующее стилю Singleton, все намного сложнее.

Импорт с помощью требований политики создания

CreationPolicy.Any явно указывает на то, что политика создания части не определена, и что жизненный цикл будет определен посредством сопоставления атрибутов экспорта и импорта.

Среди многочисленных вариантов импорта зависимостей в MEF есть возможность, позволяющая нам потребовать, чтобы часть имела конкретную политику создания. Это могло бы выглядеть следующим образом:

[ImportingConstructor]
public Mayonnaise(
	[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
	EggYolk eggYolk,
	OliveOil oil)
{ }

Этот конструктор Mayonnaise явно указывает на то, что принимаются только свежие желтки. С точки зрения кулинарии, возможно, это и не кажется таким уж сложным, но, когда дело касается кода, это требование задает строгое ограничение для импортируемой части. Это требование компилируется в класс Mayonnaise посредством атрибута [Import], которым отмечается аргумент конструктора eggYolk. Обратите внимание на то, что этим атрибутом отмечен только атрибут eggYolk, что позволяет вам для создания нескольких порций майонеза брать оливковое масло из той же самой бутылки.

Предупреждение

Задание требования политики создания в компилируемом атрибуте потребителя – это разновидность анти-паттерна Control Freak. MEF позволяет это сделать, но вам необходимо воздержаься от такого поведения, поскольку это ограничивает ваши возможности компоновки.

Свойство RequiredCreationPolicy потенциально может изменить контекст сопоставления импортируемых и экспортируемых компонентов. Если мы не используем это свойство, то принимается любое значение (Shared и NonShared), но при использовании свойства RequiredCreationPolicy несовместимые экспортируемые элементы будут отбрасываться.

Помните ли вы таблицу 15-2, в которой описывается, как сопоставляются импортируемые и экспортируемые компоненты по мощности? Сопоставление по политике создания – это еще одна сторона алгоритма сопоставления, используемого в MEF. В таблице 15-5 демонстрируется, как сопоставляются политики создания.

Таблица 15-5: Сопоставление импортируемых и экспортируемых компонентов по политике создания
Export.Any Export.Shared Export.NonShared
Import.Any Shared Shared NonShared
Import.Shared Shared Shared Не сопоставимы
Import.NonShared NonShared Не сопоставимы NonShared

Не забывайте, что сопоставление по политике создания – это всего лишь одна из сторон сопоставления импортируемых и экспортируемых компонентов, и перед тем, как будет выполнено сопоставление, должны быть проверены все ограничения. Не указывать для импортируемых элементов политику создания – наиболее предпочтительный вариант.

В этом разделе вы увидели, что политика создания определяется с помощью атрибутов, в общем случае подобных атрибутам импорта и экспорта. Указание политик создания – первая составляющая процесса управления жизненным циклом. Но после разрешения диаграмм объектов, которые могут содержать части, имеющие смешанные жизненные циклы, мы должны не забыть их высвободить.

Высвобождение объектов

Как мы уже обсуждали в разделе 8.2.2 "Управление устраняемыми зависимостями", важно высвободить объекты после того, как мы завершили работу с ними, чтобы можно было уничтожить любые устраняемые экземпляры при завершении их жизненного цикла. В MEF выполнить это довольно легко. Мы можем явным образом высвободить экспортируемые элементы или же устранить весь контейнер, если он нам больше не нужен.

Высвобождение экспортируемых компонентов

Высвобождать экспортируемые компоненты очень легко, но особенностью MEF является то, что, несмотря на возможность высвобождать экспортируемые компоненты, мы не можем высвобожlать экспортированные значения. В чем отличие?

Ниже приведено экспортированное значение:

var ingredient = container.GetExportedValue<IIngredient>();

Как вы уже видели в разделе 15.1.1 "Разрешение объектов", метод GetExportedValue возвращает экземпляр запрашиваемого типа, таким образом, ingredient является экземпляром IIngredient. Вместо того чтобы запрашивать экспортированное значение, можно запросить экспортируемый компонент:

var x = container.GetExport<IIngredient>();

Метод GetExport возвращает Lazy<IIngredient>, а не экземпляр IIngredient. Однако вы можете получить экспортированное значение из свойства Value экспортируемого компонента:

var ingredient = x.Value;

Поскольку x – это экземпляр Lazy<IIngredient>, ingredient является экземпляром IIngredient. Если вы собираетесь высвобождать разрешенные компоненты, вы должны использовать экспортируемые компоненты, поскольку для CompositionContainer возможен только один метод Release:

public void ReleaseExport<T>(Lazy<T> export)

Для метода ReleaseExport нужен экспортируемый компонент и неэкспортированное значение. Это означает, что вы не можете напрямую высвобождать экспортированное значение посредством передачи переменной ingredient, а для того, чтобы высвободить его, должны использовать экспортируемый компонент:

container.ReleaseExport(x);

Поскольку ingredient был создан из x, он высвобождается, когда высвобождается подобный экспортируемый компонент. Устраняемые зависимости должным образом уничтожаются, если заканчивается их жизненный цикл. Высвобождать части столь же просто, как и вызывать метод ReleaseExport. Но для того чтобы иметь возможность вызывать этот метод, вы должны использовать первоначальный экспортируемый компонент.

В сценариях, когда один и тот же контейнер разрешает множество экземпляров, важно иметь возможность высвобождать экспортируемые компоненты, не устраняя при этом сам контейнер. Такой сценарий обычно используется в веб-приложениях и веб-сервисах, в которых один и тот же контейнер управляет сразу несколькими запросами. С другой стороны, в клиентских приложениях необходимо разрешать только одну диаграмму зависимостей, при этом мы можем уничтожить контейнер после закрытия приложения.

Высвобождение контейнера

Такие клиентские приложения, как WPF, Windows Forms или консольные приложения, должны руководствоваться простым паттерном Register Release Resolve, создавая только одну диаграмму объектов для всего жизненного цикла приложения. Это означает, что нам нужно всего лишь высвободить диаграмму объектов после окончания жизненного цикла приложения.

Несмотря на то, что мы можем высвободить экспортируемый компонент с помощью метода ReleaseExport, проще устранить сам контейнер, и при этом нам не придется хранить ссылку на экспортируемый компонент. После закрытия приложения контейнер уже не нужен, поэтому мы можем соответствующим образом высвободить все части посредством уничтожения самого контейнера:

container.Dispose();

При устранении контейнера высвобождаются все части, при этом устраняются как Singleton-, так и Transient-компоненты.

На этом наше путешествие по процессу управления жизненным циклом в MEF подходит к концу. Части можно компоновать из компонентов со смешанными стилями существования, и это справедливо даже тогда, когда мы определяем несколько экспортируемых компонентов одной и той же абстракции. Мы еще не рассматривали процесс работы с составными частями, поэтому давайте обратим наше внимание на эту тему.