10

我使用 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...

这里的实例有许多其他由 MEF 通过 CTOR 注入的组件。这些组件还包含由 MEF 注入的其他组件。问题在于,由于某些实例是共享的,因此很难决定何时处置组件。如果我在其中一个上调用 Dispose,这将导致其他人无法使用它。正如您在这张图片中看到的,实例由 MEF 创建并注入到我的应用程序类中。每个组件不应该对其他组件有任何了解,并且应该使用注入的组件来完成工作。

我不确定当应用程序终止或容器被处置时,我应该在哪里/如何指示 MEF 在组件上调用 Dispose?我应该在组件上调用 Dispose 吗?我认为这是不对的,因为 MEF 创建它们并根据需要将它们注入客户端。客户在完成工作时不应调用他们的 Dispose。

4

2 回答 2

7

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,然后对所有请求使用同一个。

于 2010-02-24T21:21:51.107 回答
1

丹尼尔是对的。我将 Import 和 Export 的关系定义为我的映射类中的属性。我将它们作为 ComposablePartCatalog 加载到 MEF 的容器中,以便 MEF 可以神奇地即时获取相应的实例。在映射类中,我有一些新实例的代码。因此,我必须找到一种方法,让 MEF 在 MEF 退出进程时回调那些映射类以处置创建的资源。

我喜欢 Daniel 的建议,即为我的导出部分引入一个类。由于我所有的 DI 映射都是以属性(getter 和 setter)的方式定义的,所以我创建了一个这样的基类:

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

顺便说一句,我喜欢使用属性作为我的导入和导出,因为属性与类的业务逻辑无关。在其他许多情况下,某些类来自第三方,我无权访问它们的源代码以将它们标记为导出。

于 2010-02-25T20:30:18.923 回答