Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В управляемой платформе расширяемости (MEF) модель программирования — это конкретный метод определения набора концептуальных объектов, на которых работает MEF. Эти концептуальные объекты включают части, импорт и экспорт. MEF использует эти объекты, но не указывает, как они должны быть представлены. Таким образом, можно использовать широкий спектр моделей программирования, включая настраиваемые модели программирования.
Модель программирования по умолчанию, используемая в MEF, является моделью программирования с атрибутами. В части модели программирования атрибутов, импорт, экспорт и другие объекты определяются атрибутами, которые украшают обычные классы .NET Framework. В этом разделе объясняется, как использовать атрибуты, предоставляемые моделью программирования атрибутов для создания приложения MEF.
Основы импорта и экспорта
Экспорт — это значение, которое часть предоставляет другим частям в контейнере, а импорт — это требование, которое часть выражает в контейнере, для заполнения из доступных экспортов. В модели программирования с использованием атрибутов импорт и экспорт объявляются путем декорирования классов или членов атрибутами Import и Export. Атрибут Export может декорировать класс, поле, свойство или метод, а Import атрибут может декорировать поле, свойство или параметр конструктора.
Чтобы импорт соответствовал экспорту, импорт и экспорт должны иметь тот же контракт. Контракт состоит из строки, называемой именем контракта, и тип экспортированного или импортированного объекта, называемого типом контракта. Только если имя контракта и тип контракта совпадают, экспорт считается выполнением конкретного импорта.
Любой из параметров контракта может быть неявным или явным. В следующем коде показан класс, объявляющий базовый импорт.
Public Class MyClass1
<Import()>
Public Property MyAddin As IMyAddin
End Class
public class MyClass
{
[Import]
public IMyAddin MyAddin { get; set; }
}
В этом импорте Import атрибут не имеет ни типа контракта, ни присоединенного параметра имени контракта. Поэтому оба будут определены на основе декорированного свойства. В этом случае тип контракта будет IMyAddin, и имя контракта будет уникальной строкой на основе типа контракта. (Другими словами, название контракта будет совпадать только с экспортами, названия которых также определяются из типа IMyAddin.)
Ниже показан экспорт, соответствующий предыдущему импорту.
<Export(GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
В этом экспорте тип контракта — IMyAddin, так как он указан как параметр атрибута Export. Экспортируемый тип должен совпадать с типом контракта, производным от типа контракта или реализовывать тип контракта, если он является интерфейсом. В этом экспорте фактический тип MyLogger реализует интерфейс IMyAddin. Имя контракта выводится из типа контракта, что означает, что этот экспорт будет соответствовать предыдущему импорту.
Замечание
Экспорт и импорт обычно следует объявлять в классах или членах, доступных для общего пользования. Поддерживаются другие объявления, но экспорт или импорт частного, защищенного или внутреннего элемента нарушает модель изоляции для части и поэтому не рекомендуется.
Тип контракта должен точно соответствовать экспорту и импорту, чтобы считаться совпадением. Рассмотрим следующий экспорт.
<Export()> 'WILL NOT match the previous import!
Public Class MyLogger
Implements IMyAddin
End Class
[Export] //WILL NOT match the previous import!
public class MyLogger : IMyAddin { }
В этом экспорте тип контракта MyLogger заменен на IMyAddin. Несмотря на то, что MyLogger реализует IMyAddin и поэтому может быть приведён к IMyAddin объекту, этот экспорт не соответствует предыдущему импорту, поскольку типы контрактов различны.
Как правило, не обязательно указывать имя контракта, и большинство контрактов должны определяться с точки зрения типа контракта и метаданных. Однако при определенных обстоятельствах важно указать имя контракта напрямую. Наиболее распространенным случаем является то, что класс экспортирует несколько значений, которые используют общий тип, например примитивы. Имя контракта можно указать в качестве первого параметра атрибута Import или Export. В следующем коде показан импорт и экспорт с указанным именем MajorRevisionконтракта.
Public Class MyExportClass
'This one will match
<Export("MajorRevision")>
Public ReadOnly Property MajorRevision As Integer
Get
Return 4
End Get
End Property
<Export("MinorRevision")>
Public ReadOnly Property MinorRevision As Integer
Get
Return 16
End Get
End Property
End Class
public class MyClass
{
[Import("MajorRevision")]
public int MajorRevision { get; set; }
}
public class MyExportClass
{
[Export("MajorRevision")] //This one will match.
public int MajorRevision = 4;
[Export("MinorRevision")]
public int MinorRevision = 16;
}
Если тип контракта не указан, он по-прежнему будет выводиться из типа импорта или экспорта. Однако даже если имя контракта указано явно, тип контракта также должен точно совпадать, чтобы импорт и экспорт считались совпадением. Например, если MajorRevision поле было строкой, выводимые типы контрактов не будут совпадать, а экспорт не будет соответствовать импорту, несмотря на то, что имя контракта совпадает.
Импорт и экспорт метода
Атрибут Export также может декорировать метод так же, как класс, свойство или функцию. Экспорт методов должен указывать тип контракта или имя контракта, так как тип не может быть выведен. Указанный тип может быть пользовательским делегатом или универсальным типом, например Func. Следующий класс экспортирует метод с именем DoSomething.
Public Class MyAddin
'Explicitly specifying a generic type
<Export(GetType(Func(Of Integer, String)))>
Public Function DoSomething(ByVal TheParam As Integer) As String
Return Nothing 'Function body goes here
End Function
End Class
public class MyAddin
{
//Explicitly specifying a generic type.
[Export(typeof(Func<int, string>))]
public string DoSomething(int TheParam);
}
В этом классе DoSomething метод принимает один int параметр и возвращает stringзначение. Чтобы соответствовать этому экспорту, импортируемая часть должна объявить соответствующий член. Следующий класс импортирует DoSomething метод.
Public Class MyClass1
'Contract name must match!
<Import()>
Public Property MajorRevision As Func(Of Integer, String)
End Class
public class MyClass
{
[Import] //Contract name must match!
public Func<int, string> DoSomething { get; set; }
}
Дополнительные сведения об использовании Func<T, T> объекта см. в разделе Func<T,TResult>.
Типы импорта
MEF поддерживает несколько типов импорта, включая динамические, ленивые, предварительные и необязательные.
Динамические импорты
В некоторых случаях импортирующий класс может захотеть сопоставить экспорты любого типа с определенным именем контракта. В этом сценарии класс может объявить динамический импорт. Следующий импорт соответствует любому экспорту с именем контракта TheString.
Public Class MyClass1
<Import("TheString")>
Public Property MyAddin
End Class
public class MyClass
{
[Import("TheString")]
public dynamic MyAddin { get; set; }
}
Если тип контракта выводится из ключевого слова, он будет соответствовать любому типу dynamic контракта. В этом случае импорт должен всегда указывать имя контракта для сопоставления. (Если имя контракта не указано, импорт будет считаться не соответствующим экспорту.) Оба следующих экспорта соответствуют предыдущему импорту.
<Export("TheString", GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
<Export("TheString")>
Public Class MyToolbar
End Class
[Export("TheString", typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
[Export("TheString")]
public class MyToolbar { }
Очевидно, что импортируемый класс должен быть готов к борьбе с объектом произвольного типа.
Отложенные импорты
В некоторых случаях для импортируемого класса может потребоваться непрямая ссылка на импортированный объект, чтобы объект не был немедленно создан. В этом сценарии класс может объявить отложенный импорт с помощью типа контракта Lazy<T>. Следующее свойство импорта объявляет отложенный импорт.
Public Class MyClass1
<Import()>
Public Property MyAddin As Lazy(Of IMyAddin)
End Class
public class MyClass
{
[Import]
public Lazy<IMyAddin> MyAddin { get; set; }
}
С точки зрения механизма композиции тип Lazy<T> контракта считается идентичным типу Tконтракта. Таким образом, предыдущий импорт будет соответствовать следующему экспорту.
<Export(GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
Имя контракта и тип контракта можно указать в атрибуте Import для отложенного импорта, как описано ранее в разделе "Базовый импорт и экспорт".
Необходимые импорты
Экспортированные части MEF обычно создаются композиционным двигателем в ответ на прямой запрос или необходимость заполнения соответствующего импорта. По умолчанию при создании части подсистема композиции использует конструктор без параметров. Чтобы модуль использовал другой конструктор, его можно пометить атрибутом ImportingConstructor .
Каждая часть может иметь только один конструктор для использования подсистемой композиции. Предоставление без параметров конструктора и без ImportingConstructor атрибута или предоставления нескольких ImportingConstructor атрибутов приведет к ошибке.
Чтобы заполнить параметры конструктора, помеченного атрибутом ImportingConstructor, все эти параметры автоматически объявляются импортами. Это удобный способ объявления импортов, используемых в процессе инициализации компонентов. В следующем классе используется ImportingConstructor для объявления импорта.
Public Class MyClass1
Private _theAddin As IMyAddin
'Parameterless constructor will NOT be used
'because the ImportingConstructor
'attribute is present.
Public Sub New()
End Sub
'This constructor will be used.
'An import with contract type IMyAddin
'is declared automatically.
<ImportingConstructor()>
Public Sub New(ByVal MyAddin As IMyAddin)
_theAddin = MyAddin
End Sub
End Class
public class MyClass
{
private IMyAddin _theAddin;
//Parameterless constructor will NOT be
//used because the ImportingConstructor
//attribute is present.
public MyClass() { }
//This constructor will be used.
//An import with contract type IMyAddin is
//declared automatically.
[ImportingConstructor]
public MyClass(IMyAddin MyAddin)
{
_theAddin = MyAddin;
}
}
По умолчанию ImportingConstructor атрибут использует выводимые типы контрактов и имена контрактов для всех импортируемых параметров. Это можно переопределить, декорируя параметры атрибутами Import , которые затем могут определять тип контракта и имя контракта явным образом. Следующий код демонстрирует конструктор, который использует этот синтаксис для импорта производного класса вместо родительского класса.
<ImportingConstructor()>
Public Sub New(<Import(GetType(IMySubAddin))> ByVal MyAddin As IMyAddin)
End Sub
[ImportingConstructor]
public MyClass([Import(typeof(IMySubAddin))]IMyAddin MyAddin)
{
_theAddin = MyAddin;
}
В частности, следует внимательно следить за параметрами коллекции. Например, если вы указываете ImportingConstructor в конструкторе с параметром типа IEnumerable<int>, импорт будет соответствовать одному экспорту типа IEnumerable<int>, а не набору экспортов типа int. Чтобы сопоставить набор экспортов типа int, необходимо украсить параметр атрибутом ImportMany .
Параметры, объявленные в качестве импорта атрибутом ImportingConstructor , также помечены как необходимые импорты. MEF обычно позволяет, чтобы экспорт и импорт формировали цикл. Например, цикл заключается в том, где объект A импортирует объект B, который, в свою очередь, импортирует объект A. В обычных обстоятельствах цикл не является проблемой, и контейнер композиции обычно создает оба объекта.
Если импортированное значение требуется конструктором части, этот объект не может участвовать в цикле. Если объект A требует, чтобы объект B был создан прежде, чем его можно будет создать, и объект B импортирует объект A, то цикл станет неразрешимым, и возникнет ошибка композиции. Импорты, объявленные в параметрах конструктора, являются необходимыми условиями, которые должны быть выполнены перед использованием экспортов из объекта, требующего их.
Необязательные импорты
Атрибут Import указывает требование для функции части. Если импорт не может быть выполнен, создание этой части завершится неудачей, и часть не будет доступна.
Можно указать, что импорт необязателен с помощью AllowDefault свойства. В этом случае композиция будет выполнена успешно, даже если импорт не соответствует доступным экспортам, а импортируемое свойство будет задано по умолчанию для его типа свойства (null для свойств объекта, false для логических свойств или ноль для числовых свойств). Следующий класс использует необязательный импорт.
Public Class MyClass1
<Import(AllowDefault:=True)>
Public Property thePlugin As Plugin
'If no matching export is available,
'thePlugin will be set to null.
End Class
public class MyClass
{
[Import(AllowDefault = true)]
public Plugin thePlugin { get; set; }
//If no matching export is available,
//thePlugin will be set to null.
}
Импорт нескольких объектов
Атрибут Import будет успешно создан только в том случае, если он соответствует одному и только одному экспорту. В других случаях возникает ошибка композиции. Чтобы импортировать несколько экспортов, соответствующих одному контракту ImportMany , используйте атрибут. Импорт, помеченный этим атрибутом, всегда необязателен. Например, композиция не завершится ошибкой, если отсутствуют подходящие экспортируемые элементы. Следующий класс импортирует любое количество экспортов типа IMyAddin.
Public Class MyClass1
<ImportMany()>
Public Property MyAddin As IEnumerable(Of IMyAddin)
End Class
public class MyClass
{
[ImportMany]
public IEnumerable<IMyAddin> MyAddin { get; set; }
}
К импортированному массиву можно получить доступ с помощью обычного IEnumerable<T> синтаксиса и методов. Вместо этого можно использовать обычный массив (IMyAddin[]).
Этот шаблон может быть очень важным при использовании его в сочетании с синтаксисом Lazy<T> . Например, с помощью ImportMany, IEnumerable<T>и Lazy<T>можно импортировать косвенные ссылки на любое количество объектов и создавать экземпляры только необходимых. В следующем классе показан этот шаблон.
Public Class MyClass1
<ImportMany()>
Public Property MyAddin As IEnumerable(Of Lazy(Of IMyAddin))
End Class
public class MyClass
{
[ImportMany]
public IEnumerable<Lazy<IMyAddin>> MyAddin { get; set; }
}
Предотвращение обнаружения
В некоторых случаях может потребоваться запретить обнаружение части в рамках каталога. Например, часть может быть базовым классом, предназначенным для наследования, но не для использования. Это можно сделать двумя способами. Во-первых, ключевое abstract слово можно использовать в классе компонента. Абстрактные классы никогда не предоставляют экспорт, хотя они могут предоставлять унаследованные экспорты классам, производным от них.
Если класс нельзя сделать абстрактным, его можно декорировать атрибутом PartNotDiscoverable . Часть, украшенная этим атрибутом, не будет включена в каталоги. В следующем примере показаны эти шаблоны.
DataOne будет обнаружен каталогом. Так как DataTwo это абстрактно, оно не будет обнаружено. Поскольку DataThree использовал атрибут PartNotDiscoverable, он не будет обнаружен.
<Export()>
Public Class DataOne
'This part will be discovered
'as normal by the catalog.
End Class
<Export()>
Public MustInherit Class DataTwo
'This part will not be discovered
'by the catalog.
End Class
<PartNotDiscoverable()>
<Export()>
Public Class DataThree
'This part will also not be discovered
'by the catalog.
End Class
[Export]
public class DataOne
{
//This part will be discovered
//as normal by the catalog.
}
[Export]
public abstract class DataTwo
{
//This part will not be discovered
//by the catalog.
}
[PartNotDiscoverable]
[Export]
public class DataThree
{
//This part will also not be discovered
//by the catalog.
}
Метаданные и представления метаданных
Экспорты могут предоставлять дополнительные сведения о себе, известные как метаданные. Метаданные можно использовать для передачи свойств экспортированного объекта в импортируемую часть. Импортируемая часть может использовать эти данные, чтобы решить, какие экспорты следует использовать, или собирать сведения об экспорте без необходимости создавать его. По этой причине импорт должен быть ленивым для использования метаданных.
Для использования метаданных обычно объявляется интерфейс, известный как представление метаданных, который объявляет, какие метаданные будут доступны. Интерфейс представления метаданных должен иметь только свойства, и эти свойства должны иметь get методы доступа. Следующий интерфейс — это пример представления метаданных.
Public Interface IPluginMetadata
ReadOnly Property Name As String
<DefaultValue(1)>
ReadOnly Property Version As Integer
End Interface
public interface IPluginMetadata
{
string Name { get; }
[DefaultValue(1)]
int Version { get; }
}
Кроме того, можно использовать универсальную коллекцию в IDictionary<string, object>качестве представления метаданных, но это позволяет избежать преимуществ проверки типов.
Обычно требуются все свойства, именованные в представлении метаданных, и все экспорты, которые не предоставляют их, не будут считаться совпадением. Атрибут DefaultValue указывает, что свойство является необязательным. Если свойство не включено, оно будет присвоено значение по умолчанию, указанное в качестве параметра DefaultValue. Ниже приведены два разных класса, украшенных метаданными. Оба этих класса соответствуют предыдущему представлению метаданных.
<Export(GetType(IPlugin))>
<ExportMetadata("Name", "Logger")>
<ExportMetadata("Version", 4)>
Public Class MyLogger
Implements IPlugin
End Class
'Version is not required because of the DefaultValue
<Export(GetType(IPlugin))>
<ExportMetadata("Name", "Disk Writer")>
Public Class DWriter
Implements IPlugin
End Class
[Export(typeof(IPlugin)),
ExportMetadata("Name", "Logger"),
ExportMetadata("Version", 4)]
public class Logger : IPlugin
{
}
[Export(typeof(IPlugin)),
ExportMetadata("Name", "Disk Writer")]
//Version is not required because of the DefaultValue
public class DWriter : IPlugin
{
}
Метаданные выражаются после атрибута Export с помощью атрибута ExportMetadata . Каждая часть метаданных состоит из пары "имя-значение". Часть имени метаданных должна соответствовать имени соответствующего свойства в представлении метаданных, а значение будет назначено данному свойству.
Это средство импорта, указывающее, какое представление метаданных, если таковой имеется, будет использоваться. Импорт с метаданными определен как ленивый импорт, при этом интерфейс метаданных является вторым типовым параметром в Lazy<T,T>. Следующий класс импортирует предыдущую часть с метаданными.
Public Class Addin
<Import()>
Public Property plugin As Lazy(Of IPlugin, IPluginMetadata)
End Class
public class Addin
{
[Import]
public Lazy<IPlugin, IPluginMetadata> plugin;
}
Во многих случаях необходимо объединить метаданные с ImportMany атрибутом, чтобы проанализировать доступные импорты и выбрать и создать экземпляр только один или отфильтровать коллекцию, чтобы соответствовать определенному условию. Следующий класс создает экземпляры только тех IPlugin объектов, которые имеют значение Name "Логгер".
Public Class User
<ImportMany()>
Public Property plugins As IEnumerable(Of Lazy(Of IPlugin, IPluginMetadata))
Public Function InstantiateLogger() As IPlugin
Dim logger As IPlugin
logger = Nothing
For Each Plugin As Lazy(Of IPlugin, IPluginMetadata) In plugins
If Plugin.Metadata.Name = "Logger" Then
logger = Plugin.Value
End If
Next
Return logger
End Function
End Class
public class User
{
[ImportMany]
public IEnumerable<Lazy<IPlugin, IPluginMetadata>> plugins;
public IPlugin InstantiateLogger()
{
IPlugin logger = null;
foreach (Lazy<IPlugin, IPluginMetadata> plugin in plugins)
{
if (plugin.Metadata.Name == "Logger")
logger = plugin.Value;
}
return logger;
}
}
Импорт и экспорт наследования
Если класс наследует от части, этот класс также может стать частью. Импорт всегда наследуется подклассами. Поэтому подкласс части всегда будет частью с тем же импортом, что и родительский класс.
Экспорт, объявленный с помощью атрибута Export , не наследуется подклассами. Однако часть может экспортировать себя с помощью атрибута InheritedExport . Подклассы части наследуются и предоставляют тот же экспорт, включая имя контракта и тип контракта. В отличие от атрибута Export , InheritedExport можно применять только на уровне класса, а не на уровне члена. Поэтому экспорт на уровне члена никогда не может быть унаследован.
Следующие четыре класса демонстрируют принципы наследования импорта и экспорта.
NumTwo наследует от NumOne, поэтому NumTwo будет импортирован IMyData. Обычные экспорты не наследуются, поэтому NumTwo не будут экспортировать ничего.
NumFour наследует от NumThree. Так как NumThree используется InheritedExport, NumFour имеет один экспорт с типом контракта NumThree. Экспорт на уровне члена никогда не наследуется, поэтому IMyData не экспортируется.
<Export()>
Public Class NumOne
<Import()>
Public Property MyData As IMyData
End Class
Public Class NumTwo
Inherits NumOne
'Imports are always inherited, so NumTwo will
'Import IMyData
'Ordinary exports are not inherited, so
'NumTwo will NOT export anything. As a result it
'will not be discovered by the catalog!
End Class
<InheritedExport()>
Public Class NumThree
<Export()>
Public Property MyData As IMyData
'This part provides two exports, one of
'contract type NumThree, and one of
'contract type IMyData.
End Class
Public Class NumFour
Inherits NumThree
'Because NumThree used InheritedExport,
'this part has one export with contract
'type NumThree.
'Member-level exports are never inherited,
'so IMyData is not exported.
End Class
[Export]
public class NumOne
{
[Import]
public IMyData MyData { get; set; }
}
public class NumTwo : NumOne
{
//Imports are always inherited, so NumTwo will
//import IMyData.
//Ordinary exports are not inherited, so
//NumTwo will NOT export anything. As a result it
//will not be discovered by the catalog!
}
[InheritedExport]
public class NumThree
{
[Export]
Public IMyData MyData { get; set; }
//This part provides two exports, one of
//contract type NumThree, and one of
//contract type IMyData.
}
public class NumFour : NumThree
{
//Because NumThree used InheritedExport,
//this part has one export with contract
//type NumThree.
//Member-level exports are never inherited,
//so IMyData is not exported.
}
При наличии метаданных, связанных с атрибутом InheritedExport , эти метаданные также будут унаследованы. (Дополнительные сведения, см. в предыдущем разделе "Метаданные и представления метаданных"). Унаследованные метаданные не могут быть изменены подклассом. Однако путем повторного объявления InheritedExport атрибута с тем же именем контракта и типом контракта, но с новыми метаданными подкласс может заменить унаследованные метаданные новыми метаданными. Следующий класс демонстрирует этот принцип. Часть MegaLogger наследуется от Logger и включает атрибут InheritedExport. Так как MegaLogger повторно объявляет новые метаданные под именем Status, оно не наследует метаданные Name и Version от Logger.
<InheritedExport(GetType(IPlugin))>
<ExportMetadata("Name", "Logger")>
<ExportMetadata("Version", 4)>
Public Class Logger
Implements IPlugin
'Exports with contract type IPlugin
'and metadata "Name" and "Version".
End Class
Public Class SuperLogger
Inherits Logger
'Exports with contract type IPlugin and
'metadata "Name" and "Version", exactly the same
'as the Logger class.
End Class
<InheritedExport(GetType(IPlugin))>
<ExportMetadata("Status", "Green")>
Public Class MegaLogger
Inherits Logger
'Exports with contract type IPlugin and
'metadata "Status" only. Re-declaring
'the attribute replaces all metadata.
End Class
[InheritedExport(typeof(IPlugin)),
ExportMetadata("Name", "Logger"),
ExportMetadata("Version", 4)]
public class Logger : IPlugin
{
//Exports with contract type IPlugin and
//metadata "Name" and "Version".
}
public class SuperLogger : Logger
{
//Exports with contract type IPlugin and
//metadata "Name" and "Version", exactly the same
//as the Logger class.
}
[InheritedExport(typeof(IPlugin)),
ExportMetadata("Status", "Green")]
public class MegaLogger : Logger {
//Exports with contract type IPlugin and
//metadata "Status" only. Re-declaring
//the attribute replaces all metadata.
}
При повторном объявлении атрибута InheritedExport для переопределения метаданных убедитесь, что типы контрактов одинаковы. (В предыдущем примере IPlugin — это тип контракта.) Если они отличаются, вместо переопределения второй атрибут создаст второй независимый экспорт из компонента. Как правило, это означает, что при переопределении InheritedExport атрибута необходимо явно указать тип контракта, как показано в предыдущем примере.
Так как интерфейсы нельзя создавать напрямую, они обычно не могут быть помечены атрибутами Export или Import. Однако интерфейс может быть украшен InheritedExport атрибутом на уровне интерфейса, и экспорт вместе с любыми связанными метаданными будет унаследован любыми классами реализации. Однако сам интерфейс не будет доступен как часть.
Настраиваемые атрибуты экспорта
Основные атрибуты Export экспорта и InheritedExportмогут быть расширены для включения метаданных в качестве свойств атрибута. Этот метод полезен для применения аналогичных метаданных ко многим частям или создания дерева наследования атрибутов метаданных.
Настраиваемый атрибут может указать тип контракта, имя контракта или любые другие метаданные. Чтобы определить пользовательский атрибут, класс, наследуемый от ExportAttribute (или InheritedExportAttribute) должен быть украшен атрибутом MetadataAttribute . Следующий класс определяет пользовательский атрибут.
<MetadataAttribute()>
<AttributeUsage(AttributeTargets.Class, AllowMultiple:=false)>
Public Class MyAttribute
Inherits ExportAttribute
Public Property MyMetadata As String
Public Sub New(ByVal myMetadata As String)
MyBase.New(GetType(IMyAddin))
myMetadata = myMetadata
End Sub
End Class
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MyAttribute : ExportAttribute
{
public MyAttribute(string myMetadata)
: base(typeof(IMyAddin))
{
MyMetadata = myMetadata;
}
public string MyMetadata { get; private set; }
}
Этот класс определяет настраиваемый атрибут MyAttribute с типом IMyAddin контракта и некоторыми метаданными с именем MyMetadata. Все свойства в классе, MetadataAttribute помеченном атрибутом, считаются метаданными, определенными в пользовательском атрибуте. Следующие два объявления эквивалентны.
<Export(GetType(IMyAddin))>
<ExportMetadata("MyMetadata", "theData")>
Public Property myAddin As MyAddin
<MyAttribute("theData")>
Public Property myAddin As MyAddin
[Export(typeof(IMyAddin)),
ExportMetadata("MyMetadata", "theData")]
public MyAddin myAddin { get; set; }
[MyAttribute("theData")]
public MyAddin myAddin { get; set; }
В первом объявлении тип контракта и метаданные определяются явным образом. Во втором объявлении тип контракта и метаданные заключены в настраиваемом атрибуте. Особенно в случаях, когда большое количество идентичных метаданных должно применяться ко многим частям (например, авторам или авторским правам), использование пользовательского атрибута может сэкономить много времени и дублирования. Кроме того, деревья наследования настраиваемых атрибутов можно создать, чтобы учитывать вариации.
Чтобы создать необязательные метаданные в настраиваемом атрибуте, можно использовать DefaultValue. Если этот атрибут применяется к свойству в пользовательском классе атрибутов, он указывает, что декорированное свойство является необязательным и не должно предоставляться экспортером. Если значение свойства не задано, оно будет присвоено значение по умолчанию для его типа свойства (обычно nullfalseили 0.)
Политики создания
Когда часть задает импорт, и выполняется композиция, контейнер композиции пытается найти соответствующий экспорт. Если импорт успешно сопоставляется с экспортом, импортируемый участник устанавливается в качестве экземпляра экспортированного объекта. Откуда берётся этот экземпляр, контролируется политикой создания экспортирующей части.
Две возможные политики создания являются общими и не общими. Часть с политикой создания общего доступа будет использоваться между каждым импортом в контейнере для части с этим контрактом. Когда обработчик композиции находит совпадение и должен задать свойство импорта, он создаст новую копию части, только если она еще не существует; в противном случае он предоставит существующую копию. Это означает, что многие объекты могут иметь ссылки на одну и ту же часть. Такие части не должны полагаться на внутреннее состояние, которое может быть изменено из многих мест. Эта политика подходит для статических частей, частей, предоставляющих службы и части, которые используют много памяти или других ресурсов.
При каждом обнаружении соответствующего импорта для одного из его экспортов будет создана часть с политикой создания, не используемой для общего доступа. Поэтому новая копия будет создана для каждого импорта в контейнере, который соответствует одному из экспортированных контрактов части. Внутреннее состояние этих копий не будет передаваться. Эта политика подходит для частей, где для каждого импорта требуется собственное внутреннее состояние.
Импорт и экспорт могут указывать политику создания части, из значений Shared, NonShared, или Any. Значение по умолчанию — Any как для импорта, так и для экспорта. Экспорт, указывающий Shared или NonShared, будет соответствовать только импорту, который указывает то же самое или указывает Any. Аналогичным образом, импорт, который указывает Shared или NonShared будет соответствовать только экспорту, который указывает то же самое или указывает Any. Импорт и экспорт с несовместимыми политиками создания не считаются совпадением так же, как и импорт и экспорт, имя контракта или тип контракта которых не совпадают. Если и импорт, и экспорт указывают Any, или не указывают политику создания и по умолчанию Any, то политика создания по умолчанию будет совместной.
В следующем примере показано как импорт, так и экспорт, где указаны политики создания.
PartOne не указывает политику создания, поэтому по умолчанию используется Any.
PartTwo не указывает политику создания, поэтому по умолчанию используется Any. Поскольку и импорт, и экспорт по умолчанию являются Any, PartOne будут общими.
PartThree указывает политику создания Shared, поэтому PartTwo и PartThree будут использовать ту же копию PartOne.
PartFour указывает NonShared политику создания, поэтому PartFour не будет общим в PartFive.
PartSix задает NonShared политику создания.
PartFive и PartSix каждый получит отдельные копии PartFour.
PartSeven задает Shared политику создания. Поскольку PartFour не экспортируется с политикой создания Shared, импорт PartSeven не соответствует ничему и не будет заполнен.
<Export()>
Public Class PartOne
'The default creation policy for an export is Any.
End Class
Public Class PartTwo
<Import()>
Public Property partOne As PartOne
'The default creation policy for an import is Any.
'If both policies are Any, the part will be shared.
End Class
Public Class PartThree
<Import(RequiredCreationPolicy:=CreationPolicy.Shared)>
Public Property partOne As PartOne
'The Shared creation policy is explicitly specified.
'PartTwo and PartThree will receive references to the
'SAME copy of PartOne.
End Class
<Export()>
<PartCreationPolicy(CreationPolicy.NonShared)>
Public Class PartFour
'The NonShared creation policy is explicitly specified.
End Class
Public Class PartFive
<Import()>
Public Property partFour As PartFour
'The default creation policy for an import is Any.
'Since the export's creation policy was explicitly
'defined, the creation policy for this property will
'be non-shared.
End Class
Public Class PartSix
<Import(RequiredCreationPolicy:=CreationPolicy.NonShared)>
Public Property partFour As PartFour
'Both import and export specify matching creation
'policies. PartFive and PartSix will each receive
'SEPARATE copies of PartFour, each with its own
'internal state.
End Class
Public Class PartSeven
<Import(RequiredCreationPolicy:=CreationPolicy.Shared)>
Public Property partFour As PartFour
'A creation policy mismatch. Because there is no
'exported PartFour with a creation policy of Shared,
'this import does not match anything and will not be
'filled.
End Class
[Export]
public class PartOne
{
//The default creation policy for an export is Any.
}
public class PartTwo
{
[Import]
public PartOne partOne { get; set; }
//The default creation policy for an import is Any.
//If both policies are Any, the part will be shared.
}
public class PartThree
{
[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
public PartOne partOne { get; set; }
//The Shared creation policy is explicitly specified.
//PartTwo and PartThree will receive references to the
//SAME copy of PartOne.
}
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class PartFour
{
//The NonShared creation policy is explicitly specified.
}
public class PartFive
{
[Import]
public PartFour partFour { get; set; }
//The default creation policy for an import is Any.
//Since the export's creation policy was explicitly
//defined, the creation policy for this property will
//be non-shared.
}
public class PartSix
{
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
public PartFour partFour { get; set; }
//Both import and export specify matching creation
//policies. PartFive and PartSix will each receive
//SEPARATE copies of PartFour, each with its own
//internal state.
}
public class PartSeven
{
[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
public PartFour partFour { get; set; }
//A creation policy mismatch. Because there is no
//exported PartFour with a creation policy of Shared,
//this import does not match anything and will not be
//filled.
}
Жизненный цикл и удаление
Так как части размещаются в контейнере композиции, их жизненный цикл может быть более сложным, чем обычные объекты. Части могут реализовать два важных интерфейса, связанных с жизненным циклом: IDisposable и IPartImportsSatisfiedNotification.
Части, требующие выполнения работы при завершении работы или необходимые для выпуска ресурсов, должны реализовываться IDisposableкак обычно для объектов .NET Framework. Однако поскольку контейнер создает и сохраняет ссылки на части, только контейнер, которому принадлежит часть, должен вызывать метод Dispose. Сам контейнер реализует IDisposable, и, как часть его очистки в Dispose, он вызовет Dispose для всех частей, принадлежащих ему. По этой причине всегда следует удалять контейнер композиции, когда он и любые его части больше не нужны.
Для длительно существующих контейнеров композиции потребление памяти частями с политикой создания "без общего использования" может стать проблемой. Эти не общие части могут создаваться несколько раз и не будут удалены, пока сам контейнер не будет удален. Для этого контейнер предоставляет ReleaseExport метод. Вызов этого метода при экспорте, отличном от общего доступа, удаляет экспорт из контейнера композиции и удаляет его. Части, которые используются только удалённым экспортом, и так далее — вниз по иерархии, также удаляются и утилизируются. Таким образом, ресурсы можно восстановить без удаления самого контейнера композиции.
IPartImportsSatisfiedNotification содержит один метод с именем OnImportsSatisfied. Этот метод вызывается контейнером композиции на любых частях, реализующих интерфейс после завершения композиции, и импорт части готов к использованию. Части создаются композиционным движком для заполнения импорта других частей. Перед настройкой импорта части невозможно выполнить инициализацию, которая использует импортированные значения или управляет импортированными значениями в конструкторе частей, если эти значения не были указаны в качестве предварительных требований с помощью атрибута ImportingConstructor . Обычно это предпочтительный метод, но в некоторых случаях внедрение через конструктор может быть недоступно. В этих случаях инициализация может выполняться в OnImportsSatisfied, а часть должна выполнять IPartImportsSatisfiedNotification.