Утилизировать компоненты контейнером MEF? - PullRequest
10 голосов
/ 24 февраля 2010

Я использую MEF для сопоставления интерфейса с классом реализации как способ DI. Например, я использую атрибут Import для интерфейса и Export для класса реализации. Насколько я понимаю, инфраструктура 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 создает их и вводит их клиентам по мере необходимости. Клиенты не должны вызывать свое распоряжение при завершении работы.

Ответы [ 2 ]

7 голосов
/ 25 февраля 2010

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 только один раз, а затем использовать один и тот же для всех запросов.

1 голос
/ 25 февраля 2010

Даниэль прав. Я определил отношения импорта и экспорта как свойства в моих классах отображения. Я загрузил их как ComposablePartCatalog в контейнер MEF, чтобы MEF мог волшебным образом извлекать соответствующие экземпляры на лету. Именно в классах отображения у меня есть несколько кодов для новых экземпляров. Поэтому мне нужно найти способ позволить MEF перезвонить этим классам отображения, чтобы избавиться от созданных ресурсов, когда MEF находится вне процесса.

Мне нравится предложение Дэниела ввести класс для моей части экспорта. Поскольку все мои отображения DI определены в виде свойств (getter и setters), я создал базовый класс, подобный этому:

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!
     ...}
}

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

...