SatisfyImportsOnce vs ComposeParts

本文关键字:ComposeParts vs SatisfyImportsOnce | 更新日期: 2023-09-27 17:59:51

有人能解释一下SatisfyImportsOnceComposeParts之间的区别吗?

具体来说,我有一个MVC Web应用程序,我正在其中使用MEF。下面是(来自该应用程序的)一些代码,当我使用SatisfyImportsOnce时可以工作,但当我使用ComposeParts时不能工作。我的理解是ComposeParts从一组属性对象中创建可组合的部分,并将它们组合在指定的组合容器中,SatisfyImportsOnce通过使用指定的组合服务来组合指定的部分。对于我的猴子大脑来说,尽管英语不同,但它们在语义上是相同的。两者都使用CompositionContainer向导入目标吐出导出的类型。

public class FormPartCustomatorFactory
{
    [ImportMany(typeof(ICustomRenderer), AllowRecomposition = true)]
    private readonly List<Lazy<ICustomRenderer, ICustomRendererMetaData>> _rendererImports = new List<Lazy<ICustomRenderer, ICustomRendererMetaData>>();
    private readonly Dictionary<string, Lazy<ICustomRenderer, ICustomRendererMetaData>> _renderers;
    public static readonly FormPartCustomatorFactory Instance = new FormPartCustomatorFactory();
    static CompositionContainer _container;
    private FormPartCustomatorFactory()
    {
        using (var catalog = new DirectoryCatalog(HttpRuntime.BinDirectory, "*.dll"))
        {               
            _container = new CompositionContainer(catalog);
            _container.SatisfyImportsOnce(this); // <- Works
            // _container.ComposeParts(this); // DOESN'T Work
            _renderers = _rendererImports.ToDictionary(q => q.Metadata.Name, q => q);
        }
    }
    ~FormPartCustomatorFactory()
    {
        _container.Dispose();
    }
    public static ICustomRenderer Find(string name)
    {
        return Instance._renderers[name].Value;
    }
}

SatisfyImportsOnce vs ComposeParts

SatisyImportsOnce将组成一个类型,而不注册它进行重新组合。因此,如果您打算使用不支持重新组合的类型,您可以使用SatisfyImportsOnce,它将像往常一样完成工作,但容器中的任何更改(添加新部件或删除部件),则您的实例将不会自动重新组合以提供这些新部件。

在您的实例中,您使用的是:

[ImportMany(typeof(ICustomRenderer), AllowRecomposition = true)]

但通过SatisfyImportsOnce,该导入不会被重新组合。

如果你不担心重新组合,你可以使用构造函数注入来更改代码,所以你可以这样做:

[ImportingConstructor]
public FormPartCustomatorFactory(IEnumerable<Lazy<ICustomRenderer, ICustomRendererMetadata>> renderers)
{
    if (renderers == null)
        throw new ArgumentNullException("renderers");
    _renderers = renderers.ToDictionary(r => r.Metadata.Name, r => r);
}

我建议构造函数注入的原因是,Lazy<ICustomRenderer, ICustomRendererMetadata>实例集是您的类型所需的显式依赖项,因此最好在可用状态下实例化您的类型,而不是实例化然后需要额外的步骤来准备首次使用。

这使您的FormPartCustomatorFactory类型更易于测试。为此,如果您要更改构造函数,那么使其成为单例的方法将不起作用。相反,您可以利用MEF的终身管理功能,因此可能会将您的类型重新设计为:

public interface IFormPartCustomatorFactory
{
    ICustomRenderer Find(string name);
}
[Export(typeof(IFormPartCustomerFactory)), PartCreationPolicy(CreationPolicy.Shared)]
public class FormPartCustomatorFactory : IFormPartCustomatorFactory
{
    private IEnumerable<Lazy<ICustomRenderer, ICustomRendereMetadata>> _renderers;
    [ImportingConstructor]
    public FormPartCustomatorFactory(IEnumerable<Lazy<ICustomRenderer, ICustomRendererMetadata>> renderers)
    {
        if (renderers == null)
            throw new ArgumentNullException("renderers");
        _renderers = renderers;
    }
    public ICustomRenderer Find(string name)
    {
        return _renderers
            .Where(r => r.Metadata.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)
            .Select(r => r.Value)
            .FirstOrDefault();
    }
}

这样做意味着您的类型不依赖于MEF,它可以在没有MEF的情况下使用,它更易于测试,并且CompositionContainer将管理零件的寿命,在这种情况下,CreationPolicy.Shared(导出类型的默认值)使用单例寿命策略。然后可以导入IFormPartCustomator的一个实例,也就是导入同一个singleton实例。

我还认为,称其为Factory可能是错误的,因为工厂是为创建新实例而设计的,而您的类型只会为每个ICustomRenderer创建一个实例。如果这是预期的行为,也许它最好被称为FormPartCustomatorService,实现IFormPartCusomatorService接口?如果您想每次都旋转新实例,可以查看ExportFactory<ICustomRenderer, ICustomRendererMetadata>

正如Matthew所提到的,SatisfyImportsOnce不会注册零件进行重组。这意味着MEF容器不包含对零件的引用。

此外,SatisfyImportsOnce仅满足零件的导入,但忽略其具有的任何导出。ComposeParts也会将导出添加到容器中。