Утилизировать компоненты контейнером MEF?
Я использую MEF для сопоставления интерфейса с классом реализации как способ DI. Например, я использую атрибут "Импорт" для интерфейса и "Экспорт для класса реализации". Я понимаю, что структура MEF создаст экземпляры класса реализации и удерживает их в контейнере MEF для использования или автоматической инъекции.
Некоторые из моих классов реализации реализуют интерфейс IDispose. Поскольку экземпляры создаются MEF, я думаю, что я должен позволить MEF вызывать метод Dispose, если они являются одноразовыми, когда MEF отключен. Например, в моем приложении я держу ссылку на контейнер MEF. Когда приложение завершается, я вызываю метод Dispose контейнера. Проблема в том, что мои компоненты Dispose никогда не вызываются.
Ниже приведены примеры кода импорта и экспорта:
[Import]
private IMyInterface IComponent1 { get; set; }
....
[Export]
private IMyInterface Component {
get {
var instance = new MyImplemetation();
....
return instance;
}
}
....
Существует множество других определений импорта и экспорта для других сопоставлений аналогичным образом. Я построю сопоставления таким образом, чтобы MEF знала отношения и способ создания сопоставленных экземпляров. Вот несколько кодов в моем приложении для загрузки сопоставлений с помощью AssemblyCatalog:
var catalog = new AggregateCatalog();
catalog.Add (new AssemblyCatalog(Assembly.GetExecutingAssembly());
var batch = new CompositionBatch();
batch.AddPart(catalog);
// MEF container has all the mappings
var container = new CompositionContainer(catalog);
....
// Get instance from container
var instance = container.GetExportedValue<IMyInterface>();
// my instance CTOR has a contructor with several other
// implementation instances injected by interface
// instance starts to do its job and coordinates others ...
instance.Start();
....
// Finally the job is done.
// Dispose the container explicitly there.
container.Dispose();
// But my components are never disposed
// this results some connections not being closed
// file streams not being closed...
Здесь экземпляр имеет много других компонентов, введенных через CTOR с помощью MEF. Эти компоненты также содержат другие компоненты, которые вводятся MEF. Проблема в том, что очень сложно принять решение о том, когда выставлять компоненты, поскольку некоторые экземпляры разделяются. Если я вызову Dispose на одном, это может привести к тому, что другие не смогут его использовать. Как вы можете видеть на этом рисунке, экземпляры создаются MEF и вводятся в мои классы приложений. Каждый компонент не должен знать каких-либо других, и он должен использовать инъецированные компоненты для выполнения этой работы.
Я не уверен, где/как я должен поручить MEF вызывать Dispose на компонентах, когда приложение завершается или контейнер удален? Должен ли я вызвать Dispose на компонентах? Я не думаю, что это правильно, так как MEF создает их и при необходимости вводит их в клиентов. Клиенты не должны вызывать свои Dispose при завершении своих заданий.
Ответы
Ответ 1
MEF управляет временем жизни компонентов, которые он создает. Похоже, проблема в вашем примере состоит в том, что объект, который вы хотите удалить, фактически не создан MEF. Возможно, вы хотите сделать что-то вроде этого:
public class ComponentExporter : IDisposable
{
private IMyInterface _component;
[Export]
public IMyInterface Component
{
get
{
if (_component != null)
{
_component = new MyImplementation();
// ...
}
return _component;
}
}
public void Dispose()
{
if (_component != null)
{
_component.Dispose();
}
}
}
ComponentExporter - это класс, фактически созданный MEF, и если он реализует IDisposable, то MEF будет утилизировать его с контейнером. В этом примере ComponentExporter размещает созданный компонент, когда он располагается, что, скорее всего, вам нужно.
Конечно, было бы проще, если бы вы просто поместили экспорт в класс MyImplementation напрямую. Я предполагаю, что у вас есть причина не делать этого, но так оно и будет выглядеть:
[Export(typeof(IMyInterface))]
public class MyImplementation : IMyInterface, IDisposable
{
// ...
}
Несколько других примечаний к вашему коду: вам, вероятно, не нужно добавлять каталог в контейнер через партию, если вы не импортируете его где-то и не модифицируете его из частей внутри контейнера. И если вам приходится обрабатывать многие запросы и беспокоится о производительности, вы должны только создать AssemblyCatalog один раз, а затем использовать один и тот же для всех запросов.
Ответ 2
Даниэль прав. Я определил отношения импорта и экспорта как свойства в моих классах сопоставления. Я загрузил их как ComposablePartCatalog в контейнер MEF, чтобы MEF мог волшебным образом получать соответствующие экземпляры на лету. Именно в классах отображения у меня есть несколько кодов для новых экземпляров. Поэтому я должен найти способ, чтобы MEF мог возвращаться к тем классам отображения, чтобы распоряжаться созданными ресурсами, когда MEF вышел из процесса.
Мне нравится предложение Дэниела ввести класс для моей части экспорта. Поскольку все мои DI-сопоставления определяются способом свойств (getter и seters), я создал базовый класс следующим образом:
public class ComponentExporterBase: IDisposable {
private List<IDisposable> _list;
public ComponentExporterBase() {
_list = new List<IDisposable>();
}
protect void Add(IDisposable obj) {
_list.Add(obj);
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
foreach(var obj in _list) {
obj.Dispose();
}
_list.Clear();
}
}
public void Dispose() {
Dispose(true);
}
}
В этом базовом классе мои классы сопоставления смогут позволить MEF выполнять задание на удаление. Например, вот один пример:
internal class MyDIMappingClass : ComponentExporterBase {
[Import]
private IDataReader _dataReader { get; set; }
[Export]
private IController {
get {
var reader = _dataReader;
var instance = new MyMainController(reader);
base.Add(instance);
return instance;
}
...
}
Все мои классы сопоставления определяются аналогичным образом, и они намного чище. Основной принцип заключается в том, что экземпляры или ресурсы, созданные в классе, должны быть расположены внутри класса, но не инъецированы экземпляры. Таким образом, мне больше не нужно очищать любые введенные экземпляры MEF, как в этом примере:
public class MyMainController : IController {
private IDataReader _dataReader;
// dataReader is injected through CTOR
public MyMainControler(IDataReader dataReader) {
_dataReader = dataReader;
...
}
...
public void Dispose() {
// dispose only resources created in this class
// _dataReader is not disposed here or within the class!
...}
}
Кстати, мне нравится использовать свойства в качестве импорта и экспорта, поскольку атрибуты не имеют ничего общего с бизнес-логикой класса. В других случаях некоторые классы принадлежат третьим сторонам, и у меня нет доступа к их исходным кодам, чтобы отметить их как экспорт.