Утилизировать компоненты контейнером 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!
     ...}
}

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