SatisfyImportsOnce vs ComposeParts

Может кто-нибудь объяснить разницу между SatisfyImportsOnce и ComposeParts и почему кто-то будет работать там, где другой не делает?

В частности, у меня есть веб-приложение MVC, в котором я использую 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;
    }
}

Ответы

Ответ 1

SatisyImportsOnce будет составлять тип без регистрации его для перекомпоновки. Таким образом, если вы намерены использовать тип без поддержки для перекомпоновки, вы можете использовать SatisfyImportsOnce, и он будет выполнять работу как обычно, но любые изменения в контейнере (новые детали добавлены или детали удалены), тогда ваш экземпляр выиграл ' t автоматически будет предложено предложить эти новые детали.

В вашем случае вы используете:

[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, его можно использовать без него, его более проверяемым, а CompositionContainer будет управлять временем жизни части, в этом случае CreationPolicy.Shared (который по умолчанию для экспортируемых типов), использует однолетнюю стратегию жизни. Затем вы можете импортировать экземпляр IFormPartCustomator, вы импортируете один и тот же экземпляр singleton.

Я также утверждаю, что вызов его Factory, возможно, неверен, поскольку factory предназначен для создания новых экземпляров, тогда как ваш тип будет создавать только один экземпляр каждого ICustomRenderer. Если это предполагаемое поведение, возможно, было бы лучше назвать FormPartCustomatorService, который реализует интерфейс IFormPartCusomatorService? Если вы хотите каждый раз создавать новые экземпляры, вы можете посмотреть ExportFactory<ICustomRenderer, ICustomRendererMetadata>.

Ответ 2

Как упоминает Матфей, ​​ SatisfyImportsOnce не регистрирует часть для перекомпоновки. Это означает, что контейнер MEF не содержит ссылки на деталь.

Кроме того, SatisfyImportsOnce удовлетворяет только импорт части, но игнорирует любой экспорт, который он имеет. ComposeParts добавит экспорт в контейнер.