通过MEF容器配置组件?

时间:2010-02-24 18:29:36

标签: c# mef

我使用MEF将接口映射到实现类作为DI的一种方式。例如,我使用接口的Import属性和实现类的Export。我的理解是MEF框架将创建实现类实例并将它们保存在MEF的容器中以供使用或自动注入。

我的一些实现类实现了IDispose接口。由于实例是由MEF创建的,我想我应该让MEF在MEF外出时调用组件的Dispose方法(如果它们是一次性的)。例如,在我的应用程序中,我持有对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创建它们并根据需要将它们注入客户端。客户在完成工作时不应该打电话给他们。

2 个答案:

答案 0 :(得分: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,然后对所有请求使用相同的请求。

答案 1 :(得分:1)

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

我喜欢丹尼尔的建议,为我的出口部分介绍一个课程。由于我的所有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!
     ...}
}

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